@aglyn/aglyn 1.0.0-beta.143

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.
Files changed (963) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +11 -0
  3. package/package.json +66 -0
  4. package/src/index.d.ts +25 -0
  5. package/src/index.js +26 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/aglyn.d.ts +31 -0
  8. package/src/lib/aglyn.js +114 -0
  9. package/src/lib/aglyn.js.map +1 -0
  10. package/src/lib/app-utils/abuse-report.d.ts +193 -0
  11. package/src/lib/app-utils/abuse-report.js +321 -0
  12. package/src/lib/app-utils/abuse-report.js.map +1 -0
  13. package/src/lib/app-utils/accept-negotiation.d.ts +129 -0
  14. package/src/lib/app-utils/accept-negotiation.js +266 -0
  15. package/src/lib/app-utils/accept-negotiation.js.map +1 -0
  16. package/src/lib/app-utils/account-emails.d.ts +263 -0
  17. package/src/lib/app-utils/account-emails.js +264 -0
  18. package/src/lib/app-utils/account-emails.js.map +1 -0
  19. package/src/lib/app-utils/actions.d.ts +717 -0
  20. package/src/lib/app-utils/actions.js +1031 -0
  21. package/src/lib/app-utils/actions.js.map +1 -0
  22. package/src/lib/app-utils/activity-presenter.d.ts +152 -0
  23. package/src/lib/app-utils/activity-presenter.js +326 -0
  24. package/src/lib/app-utils/activity-presenter.js.map +1 -0
  25. package/src/lib/app-utils/advertising-tag-mounts.d.ts +133 -0
  26. package/src/lib/app-utils/advertising-tag-mounts.js +118 -0
  27. package/src/lib/app-utils/advertising-tag-mounts.js.map +1 -0
  28. package/src/lib/app-utils/advertising-tags.d.ts +307 -0
  29. package/src/lib/app-utils/advertising-tags.js +525 -0
  30. package/src/lib/app-utils/advertising-tags.js.map +1 -0
  31. package/src/lib/app-utils/agent-api-catalog.d.ts +94 -0
  32. package/src/lib/app-utils/agent-api-catalog.js +106 -0
  33. package/src/lib/app-utils/agent-api-catalog.js.map +1 -0
  34. package/src/lib/app-utils/agent-openapi.d.ts +103 -0
  35. package/src/lib/app-utils/agent-openapi.js +1128 -0
  36. package/src/lib/app-utils/agent-openapi.js.map +1 -0
  37. package/src/lib/app-utils/analytics-beacon.d.ts +154 -0
  38. package/src/lib/app-utils/analytics-beacon.js +152 -0
  39. package/src/lib/app-utils/analytics-beacon.js.map +1 -0
  40. package/src/lib/app-utils/analytics-environment.d.ts +133 -0
  41. package/src/lib/app-utils/analytics-environment.js +152 -0
  42. package/src/lib/app-utils/analytics-environment.js.map +1 -0
  43. package/src/lib/app-utils/analytics-events.d.ts +744 -0
  44. package/src/lib/app-utils/analytics-events.js +650 -0
  45. package/src/lib/app-utils/analytics-events.js.map +1 -0
  46. package/src/lib/app-utils/analytics-link-clicks.d.ts +102 -0
  47. package/src/lib/app-utils/analytics-link-clicks.js +267 -0
  48. package/src/lib/app-utils/analytics-link-clicks.js.map +1 -0
  49. package/src/lib/app-utils/analytics-path-key.d.ts +46 -0
  50. package/src/lib/app-utils/analytics-path-key.js +46 -0
  51. package/src/lib/app-utils/analytics-path-key.js.map +1 -0
  52. package/src/lib/app-utils/api-adapter.d.ts +47 -0
  53. package/src/lib/app-utils/api-adapter.js +367 -0
  54. package/src/lib/app-utils/api-adapter.js.map +1 -0
  55. package/src/lib/app-utils/api-idempotency.d.ts +169 -0
  56. package/src/lib/app-utils/api-idempotency.js +213 -0
  57. package/src/lib/app-utils/api-idempotency.js.map +1 -0
  58. package/src/lib/app-utils/api-plugins.d.ts +210 -0
  59. package/src/lib/app-utils/api-plugins.js +221 -0
  60. package/src/lib/app-utils/api-plugins.js.map +1 -0
  61. package/src/lib/app-utils/assist-credits.d.ts +482 -0
  62. package/src/lib/app-utils/assist-credits.js +509 -0
  63. package/src/lib/app-utils/assist-credits.js.map +1 -0
  64. package/src/lib/app-utils/attribution-attribute.d.ts +34 -0
  65. package/src/lib/app-utils/attribution-attribute.js +34 -0
  66. package/src/lib/app-utils/attribution-attribute.js.map +1 -0
  67. package/src/lib/app-utils/attribution-guard.d.ts +130 -0
  68. package/src/lib/app-utils/attribution-guard.js +410 -0
  69. package/src/lib/app-utils/attribution-guard.js.map +1 -0
  70. package/src/lib/app-utils/author-css.d.ts +123 -0
  71. package/src/lib/app-utils/author-css.js +275 -0
  72. package/src/lib/app-utils/author-css.js.map +1 -0
  73. package/src/lib/app-utils/author-html-markdown.d.ts +31 -0
  74. package/src/lib/app-utils/author-html-markdown.js +497 -0
  75. package/src/lib/app-utils/author-html-markdown.js.map +1 -0
  76. package/src/lib/app-utils/author-html.d.ts +135 -0
  77. package/src/lib/app-utils/author-html.js +1037 -0
  78. package/src/lib/app-utils/author-html.js.map +1 -0
  79. package/src/lib/app-utils/automation-placeholders.d.ts +72 -0
  80. package/src/lib/app-utils/automation-placeholders.js +207 -0
  81. package/src/lib/app-utils/automation-placeholders.js.map +1 -0
  82. package/src/lib/app-utils/bandwidth-cap.d.ts +183 -0
  83. package/src/lib/app-utils/bandwidth-cap.js +193 -0
  84. package/src/lib/app-utils/bandwidth-cap.js.map +1 -0
  85. package/src/lib/app-utils/binding-token-catalog.d.ts +82 -0
  86. package/src/lib/app-utils/binding-token-catalog.js +293 -0
  87. package/src/lib/app-utils/binding-token-catalog.js.map +1 -0
  88. package/src/lib/app-utils/binding-tokens.d.ts +157 -0
  89. package/src/lib/app-utils/binding-tokens.js +251 -0
  90. package/src/lib/app-utils/binding-tokens.js.map +1 -0
  91. package/src/lib/app-utils/breadcrumb-json-ld.d.ts +57 -0
  92. package/src/lib/app-utils/breadcrumb-json-ld.js +67 -0
  93. package/src/lib/app-utils/breadcrumb-json-ld.js.map +1 -0
  94. package/src/lib/app-utils/campaign-attribution.d.ts +117 -0
  95. package/src/lib/app-utils/campaign-attribution.js +162 -0
  96. package/src/lib/app-utils/campaign-attribution.js.map +1 -0
  97. package/src/lib/app-utils/campaign-forwarding.d.ts +220 -0
  98. package/src/lib/app-utils/campaign-forwarding.js +332 -0
  99. package/src/lib/app-utils/campaign-forwarding.js.map +1 -0
  100. package/src/lib/app-utils/campaign-membership.d.ts +109 -0
  101. package/src/lib/app-utils/campaign-membership.js +176 -0
  102. package/src/lib/app-utils/campaign-membership.js.map +1 -0
  103. package/src/lib/app-utils/campaign-touch.d.ts +206 -0
  104. package/src/lib/app-utils/campaign-touch.js +326 -0
  105. package/src/lib/app-utils/campaign-touch.js.map +1 -0
  106. package/src/lib/app-utils/child-contract-compose.d.ts +28 -0
  107. package/src/lib/app-utils/child-contract-compose.js +140 -0
  108. package/src/lib/app-utils/child-contract-compose.js.map +1 -0
  109. package/src/lib/app-utils/child-contract.d.ts +129 -0
  110. package/src/lib/app-utils/child-contract.js +153 -0
  111. package/src/lib/app-utils/child-contract.js.map +1 -0
  112. package/src/lib/app-utils/collection-delete.d.ts +113 -0
  113. package/src/lib/app-utils/collection-delete.js +120 -0
  114. package/src/lib/app-utils/collection-delete.js.map +1 -0
  115. package/src/lib/app-utils/collection-entries.d.ts +833 -0
  116. package/src/lib/app-utils/collection-entries.js +1285 -0
  117. package/src/lib/app-utils/collection-entries.js.map +1 -0
  118. package/src/lib/app-utils/collection-entry-date.d.ts +129 -0
  119. package/src/lib/app-utils/collection-entry-date.js +183 -0
  120. package/src/lib/app-utils/collection-entry-date.js.map +1 -0
  121. package/src/lib/app-utils/collection-kind.d.ts +55 -0
  122. package/src/lib/app-utils/collection-kind.js +69 -0
  123. package/src/lib/app-utils/collection-kind.js.map +1 -0
  124. package/src/lib/app-utils/collection-slug.d.ts +79 -0
  125. package/src/lib/app-utils/collection-slug.js +70 -0
  126. package/src/lib/app-utils/collection-slug.js.map +1 -0
  127. package/src/lib/app-utils/compose-layout-nodes.d.ts +147 -0
  128. package/src/lib/app-utils/compose-layout-nodes.js +261 -0
  129. package/src/lib/app-utils/compose-layout-nodes.js.map +1 -0
  130. package/src/lib/app-utils/compose-layout-props.d.ts +71 -0
  131. package/src/lib/app-utils/compose-layout-props.js +61 -0
  132. package/src/lib/app-utils/compose-layout-props.js.map +1 -0
  133. package/src/lib/app-utils/compose-reusable-components.d.ts +504 -0
  134. package/src/lib/app-utils/compose-reusable-components.js +1366 -0
  135. package/src/lib/app-utils/compose-reusable-components.js.map +1 -0
  136. package/src/lib/app-utils/compress.d.ts +35 -0
  137. package/src/lib/app-utils/compress.js +38 -0
  138. package/src/lib/app-utils/compress.js.map +1 -0
  139. package/src/lib/app-utils/consent-banner-ui.d.ts +226 -0
  140. package/src/lib/app-utils/consent-banner-ui.js +583 -0
  141. package/src/lib/app-utils/consent-banner-ui.js.map +1 -0
  142. package/src/lib/app-utils/consent-groups.d.ts +189 -0
  143. package/src/lib/app-utils/consent-groups.js +219 -0
  144. package/src/lib/app-utils/consent-groups.js.map +1 -0
  145. package/src/lib/app-utils/consent-preferences-dialog.d.ts +58 -0
  146. package/src/lib/app-utils/consent-preferences-dialog.js +106 -0
  147. package/src/lib/app-utils/consent-preferences-dialog.js.map +1 -0
  148. package/src/lib/app-utils/console-record-links.d.ts +163 -0
  149. package/src/lib/app-utils/console-record-links.js +178 -0
  150. package/src/lib/app-utils/console-record-links.js.map +1 -0
  151. package/src/lib/app-utils/console-routes.d.ts +658 -0
  152. package/src/lib/app-utils/console-routes.js +489 -0
  153. package/src/lib/app-utils/console-routes.js.map +1 -0
  154. package/src/lib/app-utils/console-widget-slot-context.d.ts +46 -0
  155. package/src/lib/app-utils/console-widget-slot-context.js +23 -0
  156. package/src/lib/app-utils/console-widget-slot-context.js.map +1 -0
  157. package/src/lib/app-utils/contact-custom-fields.d.ts +191 -0
  158. package/src/lib/app-utils/contact-custom-fields.js +349 -0
  159. package/src/lib/app-utils/contact-custom-fields.js.map +1 -0
  160. package/src/lib/app-utils/contact-holder.d.ts +52 -0
  161. package/src/lib/app-utils/contact-holder.js +98 -0
  162. package/src/lib/app-utils/contact-holder.js.map +1 -0
  163. package/src/lib/app-utils/contact-merge.d.ts +136 -0
  164. package/src/lib/app-utils/contact-merge.js +379 -0
  165. package/src/lib/app-utils/contact-merge.js.map +1 -0
  166. package/src/lib/app-utils/contacts.d.ts +554 -0
  167. package/src/lib/app-utils/contacts.js +401 -0
  168. package/src/lib/app-utils/contacts.js.map +1 -0
  169. package/src/lib/app-utils/content-author-profile.d.ts +68 -0
  170. package/src/lib/app-utils/content-author-profile.js +118 -0
  171. package/src/lib/app-utils/content-author-profile.js.map +1 -0
  172. package/src/lib/app-utils/content-authors.d.ts +662 -0
  173. package/src/lib/app-utils/content-authors.js +771 -0
  174. package/src/lib/app-utils/content-authors.js.map +1 -0
  175. package/src/lib/app-utils/content-schema-type.d.ts +81 -0
  176. package/src/lib/app-utils/content-schema-type.js +87 -0
  177. package/src/lib/app-utils/content-schema-type.js.map +1 -0
  178. package/src/lib/app-utils/contexts.d.ts +35 -0
  179. package/src/lib/app-utils/contexts.js +35 -0
  180. package/src/lib/app-utils/contexts.js.map +1 -0
  181. package/src/lib/app-utils/create-resource-uid.d.ts +18 -0
  182. package/src/lib/app-utils/create-resource-uid.js +23 -0
  183. package/src/lib/app-utils/create-resource-uid.js.map +1 -0
  184. package/src/lib/app-utils/crm-booking.d.ts +75 -0
  185. package/src/lib/app-utils/crm-booking.js +133 -0
  186. package/src/lib/app-utils/crm-booking.js.map +1 -0
  187. package/src/lib/app-utils/crm-csv.d.ts +232 -0
  188. package/src/lib/app-utils/crm-csv.js +432 -0
  189. package/src/lib/app-utils/crm-csv.js.map +1 -0
  190. package/src/lib/app-utils/crm-digest.d.ts +179 -0
  191. package/src/lib/app-utils/crm-digest.js +271 -0
  192. package/src/lib/app-utils/crm-digest.js.map +1 -0
  193. package/src/lib/app-utils/crm-email-templates.d.ts +172 -0
  194. package/src/lib/app-utils/crm-email-templates.js +382 -0
  195. package/src/lib/app-utils/crm-email-templates.js.map +1 -0
  196. package/src/lib/app-utils/crm-inbound.d.ts +245 -0
  197. package/src/lib/app-utils/crm-inbound.js +407 -0
  198. package/src/lib/app-utils/crm-inbound.js.map +1 -0
  199. package/src/lib/app-utils/crm-kinds.d.ts +73 -0
  200. package/src/lib/app-utils/crm-kinds.js +92 -0
  201. package/src/lib/app-utils/crm-kinds.js.map +1 -0
  202. package/src/lib/app-utils/crm-next-activity.d.ts +107 -0
  203. package/src/lib/app-utils/crm-next-activity.js +140 -0
  204. package/src/lib/app-utils/crm-next-activity.js.map +1 -0
  205. package/src/lib/app-utils/crm-reports.d.ts +479 -0
  206. package/src/lib/app-utils/crm-reports.js +680 -0
  207. package/src/lib/app-utils/crm-reports.js.map +1 -0
  208. package/src/lib/app-utils/crm-task-reminders.d.ts +129 -0
  209. package/src/lib/app-utils/crm-task-reminders.js +139 -0
  210. package/src/lib/app-utils/crm-task-reminders.js.map +1 -0
  211. package/src/lib/app-utils/crm.d.ts +1546 -0
  212. package/src/lib/app-utils/crm.js +1814 -0
  213. package/src/lib/app-utils/crm.js.map +1 -0
  214. package/src/lib/app-utils/csp-report.d.ts +84 -0
  215. package/src/lib/app-utils/csp-report.js +177 -0
  216. package/src/lib/app-utils/csp-report.js.map +1 -0
  217. package/src/lib/app-utils/csv-import.d.ts +192 -0
  218. package/src/lib/app-utils/csv-import.js +279 -0
  219. package/src/lib/app-utils/csv-import.js.map +1 -0
  220. package/src/lib/app-utils/dataset-csv.d.ts +109 -0
  221. package/src/lib/app-utils/dataset-csv.js +235 -0
  222. package/src/lib/app-utils/dataset-csv.js.map +1 -0
  223. package/src/lib/app-utils/dataset-models.d.ts +194 -0
  224. package/src/lib/app-utils/dataset-models.js +409 -0
  225. package/src/lib/app-utils/dataset-models.js.map +1 -0
  226. package/src/lib/app-utils/dataset-query.d.ts +57 -0
  227. package/src/lib/app-utils/dataset-query.js +126 -0
  228. package/src/lib/app-utils/dataset-query.js.map +1 -0
  229. package/src/lib/app-utils/datasets.d.ts +136 -0
  230. package/src/lib/app-utils/datasets.js +177 -0
  231. package/src/lib/app-utils/datasets.js.map +1 -0
  232. package/src/lib/app-utils/decompress.d.ts +36 -0
  233. package/src/lib/app-utils/decompress.js +32 -0
  234. package/src/lib/app-utils/decompress.js.map +1 -0
  235. package/src/lib/app-utils/dedicated-sending-domain.d.ts +47 -0
  236. package/src/lib/app-utils/dedicated-sending-domain.js +114 -0
  237. package/src/lib/app-utils/dedicated-sending-domain.js.map +1 -0
  238. package/src/lib/app-utils/deferred-image.d.ts +86 -0
  239. package/src/lib/app-utils/deferred-image.js +83 -0
  240. package/src/lib/app-utils/deferred-image.js.map +1 -0
  241. package/src/lib/app-utils/definition-canvas-tree.d.ts +61 -0
  242. package/src/lib/app-utils/definition-canvas-tree.js +126 -0
  243. package/src/lib/app-utils/definition-canvas-tree.js.map +1 -0
  244. package/src/lib/app-utils/deployment-shape.d.ts +167 -0
  245. package/src/lib/app-utils/deployment-shape.js +238 -0
  246. package/src/lib/app-utils/deployment-shape.js.map +1 -0
  247. package/src/lib/app-utils/detect-template-placeholders.d.ts +30 -0
  248. package/src/lib/app-utils/detect-template-placeholders.js +53 -0
  249. package/src/lib/app-utils/detect-template-placeholders.js.map +1 -0
  250. package/src/lib/app-utils/dmca-counter-notice.d.ts +185 -0
  251. package/src/lib/app-utils/dmca-counter-notice.js +277 -0
  252. package/src/lib/app-utils/dmca-counter-notice.js.map +1 -0
  253. package/src/lib/app-utils/docs-deflection.d.ts +156 -0
  254. package/src/lib/app-utils/docs-deflection.js +547 -0
  255. package/src/lib/app-utils/docs-deflection.js.map +1 -0
  256. package/src/lib/app-utils/docs-help.d.ts +57 -0
  257. package/src/lib/app-utils/docs-help.generated.d.ts +303 -0
  258. package/src/lib/app-utils/docs-help.generated.js +867 -0
  259. package/src/lib/app-utils/docs-help.generated.js.map +1 -0
  260. package/src/lib/app-utils/docs-help.js +46 -0
  261. package/src/lib/app-utils/docs-help.js.map +1 -0
  262. package/src/lib/app-utils/docs-index.generated.d.ts +29 -0
  263. package/src/lib/app-utils/docs-index.generated.js +8388 -0
  264. package/src/lib/app-utils/docs-index.generated.js.map +1 -0
  265. package/src/lib/app-utils/docs-retrieval.d.ts +67 -0
  266. package/src/lib/app-utils/docs-retrieval.js +188 -0
  267. package/src/lib/app-utils/docs-retrieval.js.map +1 -0
  268. package/src/lib/app-utils/document-landmark.d.ts +50 -0
  269. package/src/lib/app-utils/document-landmark.js +100 -0
  270. package/src/lib/app-utils/document-landmark.js.map +1 -0
  271. package/src/lib/app-utils/duplicate-resource.d.ts +101 -0
  272. package/src/lib/app-utils/duplicate-resource.js +206 -0
  273. package/src/lib/app-utils/duplicate-resource.js.map +1 -0
  274. package/src/lib/app-utils/dynamic-list-rule.d.ts +471 -0
  275. package/src/lib/app-utils/dynamic-list-rule.js +914 -0
  276. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -0
  277. package/src/lib/app-utils/element-animation.d.ts +200 -0
  278. package/src/lib/app-utils/element-animation.js +233 -0
  279. package/src/lib/app-utils/element-animation.js.map +1 -0
  280. package/src/lib/app-utils/element-hidden-style.d.ts +46 -0
  281. package/src/lib/app-utils/element-hidden-style.js +48 -0
  282. package/src/lib/app-utils/element-hidden-style.js.map +1 -0
  283. package/src/lib/app-utils/element-ui.d.ts +243 -0
  284. package/src/lib/app-utils/element-ui.js +459 -0
  285. package/src/lib/app-utils/element-ui.js.map +1 -0
  286. package/src/lib/app-utils/email-starter-policy.d.ts +160 -0
  287. package/src/lib/app-utils/email-starter-policy.js +327 -0
  288. package/src/lib/app-utils/email-starter-policy.js.map +1 -0
  289. package/src/lib/app-utils/email-topics.d.ts +310 -0
  290. package/src/lib/app-utils/email-topics.js +336 -0
  291. package/src/lib/app-utils/email-topics.js.map +1 -0
  292. package/src/lib/app-utils/enabled-plugins-context.d.ts +100 -0
  293. package/src/lib/app-utils/enabled-plugins-context.js +113 -0
  294. package/src/lib/app-utils/enabled-plugins-context.js.map +1 -0
  295. package/src/lib/app-utils/ensure-canvas-root.d.ts +38 -0
  296. package/src/lib/app-utils/ensure-canvas-root.js +61 -0
  297. package/src/lib/app-utils/ensure-canvas-root.js.map +1 -0
  298. package/src/lib/app-utils/entity-picker-context.d.ts +199 -0
  299. package/src/lib/app-utils/entity-picker-context.js +93 -0
  300. package/src/lib/app-utils/entity-picker-context.js.map +1 -0
  301. package/src/lib/app-utils/error-beacon.d.ts +107 -0
  302. package/src/lib/app-utils/error-beacon.js +296 -0
  303. package/src/lib/app-utils/error-beacon.js.map +1 -0
  304. package/src/lib/app-utils/expand-repeatables.d.ts +172 -0
  305. package/src/lib/app-utils/expand-repeatables.js +299 -0
  306. package/src/lib/app-utils/expand-repeatables.js.map +1 -0
  307. package/src/lib/app-utils/foreign-script.d.ts +82 -0
  308. package/src/lib/app-utils/foreign-script.js +102 -0
  309. package/src/lib/app-utils/foreign-script.js.map +1 -0
  310. package/src/lib/app-utils/form-abuse-ceiling.d.ts +146 -0
  311. package/src/lib/app-utils/form-abuse-ceiling.js +158 -0
  312. package/src/lib/app-utils/form-abuse-ceiling.js.map +1 -0
  313. package/src/lib/app-utils/form-contract.d.ts +198 -0
  314. package/src/lib/app-utils/form-contract.js +282 -0
  315. package/src/lib/app-utils/form-contract.js.map +1 -0
  316. package/src/lib/app-utils/form-dataset-binding.d.ts +50 -0
  317. package/src/lib/app-utils/form-dataset-binding.js +74 -0
  318. package/src/lib/app-utils/form-dataset-binding.js.map +1 -0
  319. package/src/lib/app-utils/forms.d.ts +727 -0
  320. package/src/lib/app-utils/forms.js +777 -0
  321. package/src/lib/app-utils/forms.js.map +1 -0
  322. package/src/lib/app-utils/functions.d.ts +188 -0
  323. package/src/lib/app-utils/functions.js +529 -0
  324. package/src/lib/app-utils/functions.js.map +1 -0
  325. package/src/lib/app-utils/guarded-version-save.d.ts +91 -0
  326. package/src/lib/app-utils/guarded-version-save.js +131 -0
  327. package/src/lib/app-utils/guarded-version-save.js.map +1 -0
  328. package/src/lib/app-utils/health-report.d.ts +1766 -0
  329. package/src/lib/app-utils/health-report.js +2072 -0
  330. package/src/lib/app-utils/health-report.js.map +1 -0
  331. package/src/lib/app-utils/host-naming.d.ts +135 -0
  332. package/src/lib/app-utils/host-naming.js +188 -0
  333. package/src/lib/app-utils/host-naming.js.map +1 -0
  334. package/src/lib/app-utils/host-permissions.d.ts +76 -0
  335. package/src/lib/app-utils/host-permissions.js +119 -0
  336. package/src/lib/app-utils/host-permissions.js.map +1 -0
  337. package/src/lib/app-utils/host-tokens.d.ts +228 -0
  338. package/src/lib/app-utils/host-tokens.js +304 -0
  339. package/src/lib/app-utils/host-tokens.js.map +1 -0
  340. package/src/lib/app-utils/idp-profile.d.ts +83 -0
  341. package/src/lib/app-utils/idp-profile.js +268 -0
  342. package/src/lib/app-utils/idp-profile.js.map +1 -0
  343. package/src/lib/app-utils/image-upload-types.d.ts +49 -0
  344. package/src/lib/app-utils/image-upload-types.js +86 -0
  345. package/src/lib/app-utils/image-upload-types.js.map +1 -0
  346. package/src/lib/app-utils/index.d.ts +18 -0
  347. package/src/lib/app-utils/index.js +23 -0
  348. package/src/lib/app-utils/index.js.map +1 -0
  349. package/src/lib/app-utils/internal-traffic.d.ts +197 -0
  350. package/src/lib/app-utils/internal-traffic.js +288 -0
  351. package/src/lib/app-utils/internal-traffic.js.map +1 -0
  352. package/src/lib/app-utils/lineal-order.d.ts +62 -0
  353. package/src/lib/app-utils/lineal-order.js +65 -0
  354. package/src/lib/app-utils/lineal-order.js.map +1 -0
  355. package/src/lib/app-utils/link-references.d.ts +36 -0
  356. package/src/lib/app-utils/link-references.js +89 -0
  357. package/src/lib/app-utils/link-references.js.map +1 -0
  358. package/src/lib/app-utils/link-target-search-context.d.ts +123 -0
  359. package/src/lib/app-utils/link-target-search-context.js +221 -0
  360. package/src/lib/app-utils/link-target-search-context.js.map +1 -0
  361. package/src/lib/app-utils/list-assignment-policy.d.ts +169 -0
  362. package/src/lib/app-utils/list-assignment-policy.js +170 -0
  363. package/src/lib/app-utils/list-assignment-policy.js.map +1 -0
  364. package/src/lib/app-utils/list-import.d.ts +158 -0
  365. package/src/lib/app-utils/list-import.js +332 -0
  366. package/src/lib/app-utils/list-import.js.map +1 -0
  367. package/src/lib/app-utils/llms-txt.d.ts +104 -0
  368. package/src/lib/app-utils/llms-txt.js +144 -0
  369. package/src/lib/app-utils/llms-txt.js.map +1 -0
  370. package/src/lib/app-utils/local-storage-budget.d.ts +75 -0
  371. package/src/lib/app-utils/local-storage-budget.js +153 -0
  372. package/src/lib/app-utils/local-storage-budget.js.map +1 -0
  373. package/src/lib/app-utils/lockdown.d.ts +698 -0
  374. package/src/lib/app-utils/lockdown.js +929 -0
  375. package/src/lib/app-utils/lockdown.js.map +1 -0
  376. package/src/lib/app-utils/margin-utilization.d.ts +267 -0
  377. package/src/lib/app-utils/margin-utilization.js +351 -0
  378. package/src/lib/app-utils/margin-utilization.js.map +1 -0
  379. package/src/lib/app-utils/markdown-lite.d.ts +297 -0
  380. package/src/lib/app-utils/markdown-lite.js +705 -0
  381. package/src/lib/app-utils/markdown-lite.js.map +1 -0
  382. package/src/lib/app-utils/marketing-consent.d.ts +704 -0
  383. package/src/lib/app-utils/marketing-consent.js +783 -0
  384. package/src/lib/app-utils/marketing-consent.js.map +1 -0
  385. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +120 -0
  386. package/src/lib/app-utils/marketplace-listing-visibility.js +139 -0
  387. package/src/lib/app-utils/marketplace-listing-visibility.js.map +1 -0
  388. package/src/lib/app-utils/marketplace-overrides.d.ts +271 -0
  389. package/src/lib/app-utils/marketplace-overrides.js +462 -0
  390. package/src/lib/app-utils/marketplace-overrides.js.map +1 -0
  391. package/src/lib/app-utils/marketplace-provenance.d.ts +150 -0
  392. package/src/lib/app-utils/marketplace-provenance.js +136 -0
  393. package/src/lib/app-utils/marketplace-provenance.js.map +1 -0
  394. package/src/lib/app-utils/marketplace-theme.d.ts +237 -0
  395. package/src/lib/app-utils/marketplace-theme.js +525 -0
  396. package/src/lib/app-utils/marketplace-theme.js.map +1 -0
  397. package/src/lib/app-utils/marketplace-update-state.d.ts +119 -0
  398. package/src/lib/app-utils/marketplace-update-state.js +132 -0
  399. package/src/lib/app-utils/marketplace-update-state.js.map +1 -0
  400. package/src/lib/app-utils/marketplace-verification.d.ts +95 -0
  401. package/src/lib/app-utils/marketplace-verification.js +81 -0
  402. package/src/lib/app-utils/marketplace-verification.js.map +1 -0
  403. package/src/lib/app-utils/measure-node-map.d.ts +76 -0
  404. package/src/lib/app-utils/measure-node-map.js +111 -0
  405. package/src/lib/app-utils/measure-node-map.js.map +1 -0
  406. package/src/lib/app-utils/media-alt.d.ts +55 -0
  407. package/src/lib/app-utils/media-alt.js +58 -0
  408. package/src/lib/app-utils/media-alt.js.map +1 -0
  409. package/src/lib/app-utils/media-asset-facts.d.ts +125 -0
  410. package/src/lib/app-utils/media-asset-facts.js +467 -0
  411. package/src/lib/app-utils/media-asset-facts.js.map +1 -0
  412. package/src/lib/app-utils/media-cdn-scope.d.ts +65 -0
  413. package/src/lib/app-utils/media-cdn-scope.js +75 -0
  414. package/src/lib/app-utils/media-cdn-scope.js.map +1 -0
  415. package/src/lib/app-utils/media-folders.d.ts +161 -0
  416. package/src/lib/app-utils/media-folders.js +230 -0
  417. package/src/lib/app-utils/media-folders.js.map +1 -0
  418. package/src/lib/app-utils/media-metadata.d.ts +277 -0
  419. package/src/lib/app-utils/media-metadata.js +388 -0
  420. package/src/lib/app-utils/media-metadata.js.map +1 -0
  421. package/src/lib/app-utils/media-picker-context.d.ts +93 -0
  422. package/src/lib/app-utils/media-picker-context.js +26 -0
  423. package/src/lib/app-utils/media-picker-context.js.map +1 -0
  424. package/src/lib/app-utils/media-quarantine.d.ts +247 -0
  425. package/src/lib/app-utils/media-quarantine.js +323 -0
  426. package/src/lib/app-utils/media-quarantine.js.map +1 -0
  427. package/src/lib/app-utils/media-ref.d.ts +661 -0
  428. package/src/lib/app-utils/media-ref.js +1055 -0
  429. package/src/lib/app-utils/media-ref.js.map +1 -0
  430. package/src/lib/app-utils/media-takedown-reach.d.ts +126 -0
  431. package/src/lib/app-utils/media-takedown-reach.js +143 -0
  432. package/src/lib/app-utils/media-takedown-reach.js.map +1 -0
  433. package/src/lib/app-utils/member-email-aliases.d.ts +180 -0
  434. package/src/lib/app-utils/member-email-aliases.js +217 -0
  435. package/src/lib/app-utils/member-email-aliases.js.map +1 -0
  436. package/src/lib/app-utils/merge-node-sx.d.ts +47 -0
  437. package/src/lib/app-utils/merge-node-sx.js +123 -0
  438. package/src/lib/app-utils/merge-node-sx.js.map +1 -0
  439. package/src/lib/app-utils/name-match.d.ts +155 -0
  440. package/src/lib/app-utils/name-match.js +175 -0
  441. package/src/lib/app-utils/name-match.js.map +1 -0
  442. package/src/lib/app-utils/name-search.d.ts +117 -0
  443. package/src/lib/app-utils/name-search.js +145 -0
  444. package/src/lib/app-utils/name-search.js.map +1 -0
  445. package/src/lib/app-utils/node-capabilities.d.ts +85 -0
  446. package/src/lib/app-utils/node-capabilities.js +88 -0
  447. package/src/lib/app-utils/node-capabilities.js.map +1 -0
  448. package/src/lib/app-utils/node-definition-sanitizer.d.ts +164 -0
  449. package/src/lib/app-utils/node-definition-sanitizer.js +402 -0
  450. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -0
  451. package/src/lib/app-utils/node-identity.d.ts +47 -0
  452. package/src/lib/app-utils/node-identity.js +50 -0
  453. package/src/lib/app-utils/node-identity.js.map +1 -0
  454. package/src/lib/app-utils/node-interactions.d.ts +102 -0
  455. package/src/lib/app-utils/node-interactions.js +192 -0
  456. package/src/lib/app-utils/node-interactions.js.map +1 -0
  457. package/src/lib/app-utils/node-url-policy.d.ts +54 -0
  458. package/src/lib/app-utils/node-url-policy.js +72 -0
  459. package/src/lib/app-utils/node-url-policy.js.map +1 -0
  460. package/src/lib/app-utils/notifications.d.ts +71 -0
  461. package/src/lib/app-utils/notifications.js +101 -0
  462. package/src/lib/app-utils/notifications.js.map +1 -0
  463. package/src/lib/app-utils/onboarding-deep-link.d.ts +120 -0
  464. package/src/lib/app-utils/onboarding-deep-link.js +142 -0
  465. package/src/lib/app-utils/onboarding-deep-link.js.map +1 -0
  466. package/src/lib/app-utils/operator-identity.d.ts +207 -0
  467. package/src/lib/app-utils/operator-identity.js +186 -0
  468. package/src/lib/app-utils/operator-identity.js.map +1 -0
  469. package/src/lib/app-utils/org-billing-doc.d.ts +231 -0
  470. package/src/lib/app-utils/org-billing-doc.js +238 -0
  471. package/src/lib/app-utils/org-billing-doc.js.map +1 -0
  472. package/src/lib/app-utils/org-override-reason.d.ts +100 -0
  473. package/src/lib/app-utils/org-override-reason.js +116 -0
  474. package/src/lib/app-utils/org-override-reason.js.map +1 -0
  475. package/src/lib/app-utils/org-permissions.d.ts +194 -0
  476. package/src/lib/app-utils/org-permissions.js +357 -0
  477. package/src/lib/app-utils/org-permissions.js.map +1 -0
  478. package/src/lib/app-utils/org-roles.d.ts +72 -0
  479. package/src/lib/app-utils/org-roles.js +131 -0
  480. package/src/lib/app-utils/org-roles.js.map +1 -0
  481. package/src/lib/app-utils/organizations.d.ts +407 -0
  482. package/src/lib/app-utils/organizations.js +511 -0
  483. package/src/lib/app-utils/organizations.js.map +1 -0
  484. package/src/lib/app-utils/page-header-actions-context.d.ts +83 -0
  485. package/src/lib/app-utils/page-header-actions-context.js +58 -0
  486. package/src/lib/app-utils/page-header-actions-context.js.map +1 -0
  487. package/src/lib/app-utils/page-header-record-context.d.ts +80 -0
  488. package/src/lib/app-utils/page-header-record-context.js +64 -0
  489. package/src/lib/app-utils/page-header-record-context.js.map +1 -0
  490. package/src/lib/app-utils/page-markdown.d.ts +103 -0
  491. package/src/lib/app-utils/page-markdown.js +385 -0
  492. package/src/lib/app-utils/page-markdown.js.map +1 -0
  493. package/src/lib/app-utils/paid-media-source.d.ts +90 -0
  494. package/src/lib/app-utils/paid-media-source.js +184 -0
  495. package/src/lib/app-utils/paid-media-source.js.map +1 -0
  496. package/src/lib/app-utils/palette-sx.d.ts +34 -0
  497. package/src/lib/app-utils/palette-sx.js +150 -0
  498. package/src/lib/app-utils/palette-sx.js.map +1 -0
  499. package/src/lib/app-utils/password-policy.d.ts +47 -0
  500. package/src/lib/app-utils/password-policy.js +46 -0
  501. package/src/lib/app-utils/password-policy.js.map +1 -0
  502. package/src/lib/app-utils/payments-configured.d.ts +84 -0
  503. package/src/lib/app-utils/payments-configured.js +88 -0
  504. package/src/lib/app-utils/payments-configured.js.map +1 -0
  505. package/src/lib/app-utils/person-erasure.d.ts +126 -0
  506. package/src/lib/app-utils/person-erasure.js +121 -0
  507. package/src/lib/app-utils/person-erasure.js.map +1 -0
  508. package/src/lib/app-utils/person-key.d.ts +32 -0
  509. package/src/lib/app-utils/person-key.js +62 -0
  510. package/src/lib/app-utils/person-key.js.map +1 -0
  511. package/src/lib/app-utils/plan-entitlements.d.ts +2756 -0
  512. package/src/lib/app-utils/plan-entitlements.js +4085 -0
  513. package/src/lib/app-utils/plan-entitlements.js.map +1 -0
  514. package/src/lib/app-utils/platform-ad-conversions.d.ts +122 -0
  515. package/src/lib/app-utils/platform-ad-conversions.js +174 -0
  516. package/src/lib/app-utils/platform-ad-conversions.js.map +1 -0
  517. package/src/lib/app-utils/platform-advertising-tags.d.ts +139 -0
  518. package/src/lib/app-utils/platform-advertising-tags.js +203 -0
  519. package/src/lib/app-utils/platform-advertising-tags.js.map +1 -0
  520. package/src/lib/app-utils/platform-brand.d.ts +151 -0
  521. package/src/lib/app-utils/platform-brand.js +175 -0
  522. package/src/lib/app-utils/platform-brand.js.map +1 -0
  523. package/src/lib/app-utils/platform-consent-default.d.ts +142 -0
  524. package/src/lib/app-utils/platform-consent-default.js +295 -0
  525. package/src/lib/app-utils/platform-consent-default.js.map +1 -0
  526. package/src/lib/app-utils/platform-marketing-consent.d.ts +151 -0
  527. package/src/lib/app-utils/platform-marketing-consent.js +182 -0
  528. package/src/lib/app-utils/platform-marketing-consent.js.map +1 -0
  529. package/src/lib/app-utils/platform-marketing-host.d.ts +33 -0
  530. package/src/lib/app-utils/platform-marketing-host.js +97 -0
  531. package/src/lib/app-utils/platform-marketing-host.js.map +1 -0
  532. package/src/lib/app-utils/platform-visitor-consent.d.ts +252 -0
  533. package/src/lib/app-utils/platform-visitor-consent.js +533 -0
  534. package/src/lib/app-utils/platform-visitor-consent.js.map +1 -0
  535. package/src/lib/app-utils/plugin-api-cross-origin.d.ts +61 -0
  536. package/src/lib/app-utils/plugin-api-cross-origin.js +202 -0
  537. package/src/lib/app-utils/plugin-api-cross-origin.js.map +1 -0
  538. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +192 -0
  539. package/src/lib/app-utils/plugin-api-rate-limit.js +270 -0
  540. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -0
  541. package/src/lib/app-utils/plugin-artifact-path.d.ts +32 -0
  542. package/src/lib/app-utils/plugin-artifact-path.js +33 -0
  543. package/src/lib/app-utils/plugin-artifact-path.js.map +1 -0
  544. package/src/lib/app-utils/plugin-bundle-checks.d.ts +148 -0
  545. package/src/lib/app-utils/plugin-bundle-checks.js +989 -0
  546. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -0
  547. package/src/lib/app-utils/plugin-manifest.d.ts +516 -0
  548. package/src/lib/app-utils/plugin-manifest.js +687 -0
  549. package/src/lib/app-utils/plugin-manifest.js.map +1 -0
  550. package/src/lib/app-utils/published-legal-pages.d.ts +71 -0
  551. package/src/lib/app-utils/published-legal-pages.js +90 -0
  552. package/src/lib/app-utils/published-legal-pages.js.map +1 -0
  553. package/src/lib/app-utils/publisher-agreement.d.ts +254 -0
  554. package/src/lib/app-utils/publisher-agreement.js +274 -0
  555. package/src/lib/app-utils/publisher-agreement.js.map +1 -0
  556. package/src/lib/app-utils/publisher-attestation.d.ts +99 -0
  557. package/src/lib/app-utils/publisher-attestation.js +158 -0
  558. package/src/lib/app-utils/publisher-attestation.js.map +1 -0
  559. package/src/lib/app-utils/recipient-email-render.d.ts +216 -0
  560. package/src/lib/app-utils/recipient-email-render.js +221 -0
  561. package/src/lib/app-utils/recipient-email-render.js.map +1 -0
  562. package/src/lib/app-utils/redispatch-caught-error.d.ts +38 -0
  563. package/src/lib/app-utils/redispatch-caught-error.js +46 -0
  564. package/src/lib/app-utils/redispatch-caught-error.js.map +1 -0
  565. package/src/lib/app-utils/registering-plugin.d.ts +19 -0
  566. package/src/lib/app-utils/registering-plugin.js +38 -0
  567. package/src/lib/app-utils/registering-plugin.js.map +1 -0
  568. package/src/lib/app-utils/release-flags.d.ts +283 -0
  569. package/src/lib/app-utils/release-flags.js +518 -0
  570. package/src/lib/app-utils/release-flags.js.map +1 -0
  571. package/src/lib/app-utils/repair-canvas-nodes.d.ts +130 -0
  572. package/src/lib/app-utils/repair-canvas-nodes.js +298 -0
  573. package/src/lib/app-utils/repair-canvas-nodes.js.map +1 -0
  574. package/src/lib/app-utils/repeat-infringer.d.ts +166 -0
  575. package/src/lib/app-utils/repeat-infringer.js +188 -0
  576. package/src/lib/app-utils/repeat-infringer.js.map +1 -0
  577. package/src/lib/app-utils/repeat-sources.d.ts +115 -0
  578. package/src/lib/app-utils/repeat-sources.js +86 -0
  579. package/src/lib/app-utils/repeat-sources.js.map +1 -0
  580. package/src/lib/app-utils/request-geo.d.ts +114 -0
  581. package/src/lib/app-utils/request-geo.js +247 -0
  582. package/src/lib/app-utils/request-geo.js.map +1 -0
  583. package/src/lib/app-utils/request-ip.d.ts +213 -0
  584. package/src/lib/app-utils/request-ip.js +344 -0
  585. package/src/lib/app-utils/request-ip.js.map +1 -0
  586. package/src/lib/app-utils/resolve-named-tokens.d.ts +26 -0
  587. package/src/lib/app-utils/resolve-named-tokens.js +59 -0
  588. package/src/lib/app-utils/resolve-named-tokens.js.map +1 -0
  589. package/src/lib/app-utils/responsive-typography-sx.d.ts +77 -0
  590. package/src/lib/app-utils/responsive-typography-sx.js +122 -0
  591. package/src/lib/app-utils/responsive-typography-sx.js.map +1 -0
  592. package/src/lib/app-utils/reusable-component-keys.d.ts +135 -0
  593. package/src/lib/app-utils/reusable-component-keys.js +140 -0
  594. package/src/lib/app-utils/reusable-component-keys.js.map +1 -0
  595. package/src/lib/app-utils/reusable-prop-values.d.ts +199 -0
  596. package/src/lib/app-utils/reusable-prop-values.js +494 -0
  597. package/src/lib/app-utils/reusable-prop-values.js.map +1 -0
  598. package/src/lib/app-utils/safe-json-ld.d.ts +29 -0
  599. package/src/lib/app-utils/safe-json-ld.js +37 -0
  600. package/src/lib/app-utils/safe-json-ld.js.map +1 -0
  601. package/src/lib/app-utils/sanitize-svg.d.ts +55 -0
  602. package/src/lib/app-utils/sanitize-svg.js +460 -0
  603. package/src/lib/app-utils/sanitize-svg.js.map +1 -0
  604. package/src/lib/app-utils/scheme-sx.d.ts +84 -0
  605. package/src/lib/app-utils/scheme-sx.js +148 -0
  606. package/src/lib/app-utils/scheme-sx.js.map +1 -0
  607. package/src/lib/app-utils/scope-tokens.d.ts +249 -0
  608. package/src/lib/app-utils/scope-tokens.js +331 -0
  609. package/src/lib/app-utils/scope-tokens.js.map +1 -0
  610. package/src/lib/app-utils/screen-link-context-value.d.ts +57 -0
  611. package/src/lib/app-utils/screen-link-context-value.js +43 -0
  612. package/src/lib/app-utils/screen-link-context-value.js.map +1 -0
  613. package/src/lib/app-utils/screen-link-context.d.ts +185 -0
  614. package/src/lib/app-utils/screen-link-context.js +304 -0
  615. package/src/lib/app-utils/screen-link-context.js.map +1 -0
  616. package/src/lib/app-utils/screen-link-value.d.ts +269 -0
  617. package/src/lib/app-utils/screen-link-value.js +384 -0
  618. package/src/lib/app-utils/screen-link-value.js.map +1 -0
  619. package/src/lib/app-utils/screen-route.d.ts +669 -0
  620. package/src/lib/app-utils/screen-route.js +751 -0
  621. package/src/lib/app-utils/screen-route.js.map +1 -0
  622. package/src/lib/app-utils/screen-seo-fields.d.ts +37 -0
  623. package/src/lib/app-utils/screen-seo-fields.js +42 -0
  624. package/src/lib/app-utils/screen-seo-fields.js.map +1 -0
  625. package/src/lib/app-utils/search-indexing.d.ts +162 -0
  626. package/src/lib/app-utils/search-indexing.js +207 -0
  627. package/src/lib/app-utils/search-indexing.js.map +1 -0
  628. package/src/lib/app-utils/seo-listing-fields.d.ts +70 -0
  629. package/src/lib/app-utils/seo-listing-fields.js +75 -0
  630. package/src/lib/app-utils/seo-listing-fields.js.map +1 -0
  631. package/src/lib/app-utils/seo-locale.d.ts +119 -0
  632. package/src/lib/app-utils/seo-locale.js +133 -0
  633. package/src/lib/app-utils/seo-locale.js.map +1 -0
  634. package/src/lib/app-utils/seo-title-variables.d.ts +149 -0
  635. package/src/lib/app-utils/seo-title-variables.js +190 -0
  636. package/src/lib/app-utils/seo-title-variables.js.map +1 -0
  637. package/src/lib/app-utils/seo-title.d.ts +70 -0
  638. package/src/lib/app-utils/seo-title.js +117 -0
  639. package/src/lib/app-utils/seo-title.js.map +1 -0
  640. package/src/lib/app-utils/server.d.ts +174 -0
  641. package/src/lib/app-utils/server.js +314 -0
  642. package/src/lib/app-utils/server.js.map +1 -0
  643. package/src/lib/app-utils/site-context.d.ts +106 -0
  644. package/src/lib/app-utils/site-context.js +100 -0
  645. package/src/lib/app-utils/site-context.js.map +1 -0
  646. package/src/lib/app-utils/sitemap.d.ts +117 -0
  647. package/src/lib/app-utils/sitemap.js +183 -0
  648. package/src/lib/app-utils/sitemap.js.map +1 -0
  649. package/src/lib/app-utils/social-image.d.ts +112 -0
  650. package/src/lib/app-utils/social-image.js +191 -0
  651. package/src/lib/app-utils/social-image.js.map +1 -0
  652. package/src/lib/app-utils/starter-templates.d.ts +99 -0
  653. package/src/lib/app-utils/starter-templates.js +579 -0
  654. package/src/lib/app-utils/starter-templates.js.map +1 -0
  655. package/src/lib/app-utils/stored-nodes.d.ts +120 -0
  656. package/src/lib/app-utils/stored-nodes.js +174 -0
  657. package/src/lib/app-utils/stored-nodes.js.map +1 -0
  658. package/src/lib/app-utils/strip-undefined.d.ts +59 -0
  659. package/src/lib/app-utils/strip-undefined.js +95 -0
  660. package/src/lib/app-utils/strip-undefined.js.map +1 -0
  661. package/src/lib/app-utils/stripe-deployment-mode.d.ts +47 -0
  662. package/src/lib/app-utils/stripe-deployment-mode.js +70 -0
  663. package/src/lib/app-utils/stripe-deployment-mode.js.map +1 -0
  664. package/src/lib/app-utils/support-tiers.d.ts +108 -0
  665. package/src/lib/app-utils/support-tiers.js +201 -0
  666. package/src/lib/app-utils/support-tiers.js.map +1 -0
  667. package/src/lib/app-utils/timezone-geo-hint.d.ts +36 -0
  668. package/src/lib/app-utils/timezone-geo-hint.js +180 -0
  669. package/src/lib/app-utils/timezone-geo-hint.js.map +1 -0
  670. package/src/lib/app-utils/upload-cors.d.ts +155 -0
  671. package/src/lib/app-utils/upload-cors.js +254 -0
  672. package/src/lib/app-utils/upload-cors.js.map +1 -0
  673. package/src/lib/app-utils/upload-inspection.d.ts +159 -0
  674. package/src/lib/app-utils/upload-inspection.js +539 -0
  675. package/src/lib/app-utils/upload-inspection.js.map +1 -0
  676. package/src/lib/app-utils/url-slug.d.ts +40 -0
  677. package/src/lib/app-utils/url-slug.js +42 -0
  678. package/src/lib/app-utils/url-slug.js.map +1 -0
  679. package/src/lib/app-utils/usage-budget.d.ts +429 -0
  680. package/src/lib/app-utils/usage-budget.js +421 -0
  681. package/src/lib/app-utils/usage-budget.js.map +1 -0
  682. package/src/lib/app-utils/variables.d.ts +96 -0
  683. package/src/lib/app-utils/variables.js +233 -0
  684. package/src/lib/app-utils/variables.js.map +1 -0
  685. package/src/lib/app-utils/video-asset-facts.d.ts +61 -0
  686. package/src/lib/app-utils/video-asset-facts.js +70 -0
  687. package/src/lib/app-utils/video-asset-facts.js.map +1 -0
  688. package/src/lib/app-utils/video-object.d.ts +89 -0
  689. package/src/lib/app-utils/video-object.js +245 -0
  690. package/src/lib/app-utils/video-object.js.map +1 -0
  691. package/src/lib/app-utils/visitor-consent.d.ts +757 -0
  692. package/src/lib/app-utils/visitor-consent.js +981 -0
  693. package/src/lib/app-utils/visitor-consent.js.map +1 -0
  694. package/src/lib/app-utils/visitor-record-ceiling.d.ts +223 -0
  695. package/src/lib/app-utils/visitor-record-ceiling.js +215 -0
  696. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -0
  697. package/src/lib/app-utils/web-vitals-rum.d.ts +53 -0
  698. package/src/lib/app-utils/web-vitals-rum.js +278 -0
  699. package/src/lib/app-utils/web-vitals-rum.js.map +1 -0
  700. package/src/lib/app-utils/webhook-delivery.d.ts +324 -0
  701. package/src/lib/app-utils/webhook-delivery.js +413 -0
  702. package/src/lib/app-utils/webhook-delivery.js.map +1 -0
  703. package/src/lib/app-utils/where-used.d.ts +57 -0
  704. package/src/lib/app-utils/where-used.js +59 -0
  705. package/src/lib/app-utils/where-used.js.map +1 -0
  706. package/src/lib/app-utils/wistia-embed.d.ts +86 -0
  707. package/src/lib/app-utils/wistia-embed.js +119 -0
  708. package/src/lib/app-utils/wistia-embed.js.map +1 -0
  709. package/src/lib/app-utils/workflows.d.ts +136 -0
  710. package/src/lib/app-utils/workflows.js +346 -0
  711. package/src/lib/app-utils/workflows.js.map +1 -0
  712. package/src/lib/canvas-manager/canvas-manager-tree.d.ts +109 -0
  713. package/src/lib/canvas-manager/canvas-manager-tree.js +139 -0
  714. package/src/lib/canvas-manager/canvas-manager-tree.js.map +1 -0
  715. package/src/lib/canvas-manager/canvas-manager.d.ts +549 -0
  716. package/src/lib/canvas-manager/canvas-manager.js +1311 -0
  717. package/src/lib/canvas-manager/canvas-manager.js.map +1 -0
  718. package/src/lib/canvas-manager/index.d.ts +20 -0
  719. package/src/lib/canvas-manager/index.js +21 -0
  720. package/src/lib/canvas-manager/index.js.map +1 -0
  721. package/src/lib/canvas-manager/stores/index.d.ts +17 -0
  722. package/src/lib/canvas-manager/stores/index.js +18 -0
  723. package/src/lib/canvas-manager/stores/index.js.map +1 -0
  724. package/src/lib/canvas-manager/stores/node.store.d.ts +30 -0
  725. package/src/lib/canvas-manager/stores/node.store.js +45 -0
  726. package/src/lib/canvas-manager/stores/node.store.js.map +1 -0
  727. package/src/lib/components-manager/components-manager.d.ts +56 -0
  728. package/src/lib/components-manager/components-manager.js +191 -0
  729. package/src/lib/components-manager/components-manager.js.map +1 -0
  730. package/src/lib/components-manager/index.d.ts +18 -0
  731. package/src/lib/components-manager/index.js +19 -0
  732. package/src/lib/components-manager/index.js.map +1 -0
  733. package/src/lib/emit-manager/emit-manager.d.ts +56 -0
  734. package/src/lib/emit-manager/emit-manager.js +60 -0
  735. package/src/lib/emit-manager/emit-manager.js.map +1 -0
  736. package/src/lib/emit-manager/index.d.ts +18 -0
  737. package/src/lib/emit-manager/index.js +19 -0
  738. package/src/lib/emit-manager/index.js.map +1 -0
  739. package/src/lib/foundation/constants/_internal.d.ts +18 -0
  740. package/src/lib/foundation/constants/_internal.js +62 -0
  741. package/src/lib/foundation/constants/_internal.js.map +1 -0
  742. package/src/lib/foundation/constants/app.d.ts +23 -0
  743. package/src/lib/foundation/constants/app.js +25 -0
  744. package/src/lib/foundation/constants/app.js.map +1 -0
  745. package/src/lib/foundation/constants/canvas.d.ts +18 -0
  746. package/src/lib/foundation/constants/canvas.js +18 -0
  747. package/src/lib/foundation/constants/canvas.js.map +1 -0
  748. package/src/lib/foundation/constants/components.d.ts +54 -0
  749. package/src/lib/foundation/constants/components.js +61 -0
  750. package/src/lib/foundation/constants/components.js.map +1 -0
  751. package/src/lib/foundation/constants/shared.d.ts +50 -0
  752. package/src/lib/foundation/constants/shared.js +49 -0
  753. package/src/lib/foundation/constants/shared.js.map +1 -0
  754. package/src/lib/foundation/constants/symbol.d.ts +63 -0
  755. package/src/lib/foundation/constants/symbol.js +75 -0
  756. package/src/lib/foundation/constants/symbol.js.map +1 -0
  757. package/src/lib/foundation/definitions/components.types.d.ts +372 -0
  758. package/src/lib/foundation/definitions/components.types.js +180 -0
  759. package/src/lib/foundation/definitions/components.types.js.map +1 -0
  760. package/src/lib/foundation/definitions/contact.types.d.ts +73 -0
  761. package/src/lib/foundation/definitions/contact.types.js +107 -0
  762. package/src/lib/foundation/definitions/contact.types.js.map +1 -0
  763. package/src/lib/foundation/definitions/host-content-collections.d.ts +137 -0
  764. package/src/lib/foundation/definitions/host-content-collections.js +219 -0
  765. package/src/lib/foundation/definitions/host-content-collections.js.map +1 -0
  766. package/src/lib/foundation/definitions/org-billing.types.d.ts +1361 -0
  767. package/src/lib/foundation/definitions/org-billing.types.js +77 -0
  768. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -0
  769. package/src/lib/foundation/definitions/organization.types.d.ts +307 -0
  770. package/src/lib/foundation/definitions/organization.types.js +42 -0
  771. package/src/lib/foundation/definitions/organization.types.js.map +1 -0
  772. package/src/lib/foundation/definitions/platform.types.d.ts +1326 -0
  773. package/src/lib/foundation/definitions/platform.types.js +124 -0
  774. package/src/lib/foundation/definitions/platform.types.js.map +1 -0
  775. package/src/lib/foundation/definitions/property-kinds.d.ts +189 -0
  776. package/src/lib/foundation/definitions/property-kinds.js +565 -0
  777. package/src/lib/foundation/definitions/property-kinds.js.map +1 -0
  778. package/src/lib/foundation/definitions/shared.d.ts +23 -0
  779. package/src/lib/foundation/definitions/shared.js +18 -0
  780. package/src/lib/foundation/definitions/shared.js.map +1 -0
  781. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +249 -0
  782. package/src/lib/foundation/definitions/write-deny-coverage.util.js +499 -0
  783. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -0
  784. package/src/lib/foundation/index.d.ts +32 -0
  785. package/src/lib/foundation/index.js +67 -0
  786. package/src/lib/foundation/index.js.map +1 -0
  787. package/src/lib/lifecycle.d.ts +24 -0
  788. package/src/lib/lifecycle.js +39 -0
  789. package/src/lib/lifecycle.js.map +1 -0
  790. package/src/lib/mst/common.d.ts +17 -0
  791. package/src/lib/mst/common.js +23 -0
  792. package/src/lib/mst/common.js.map +1 -0
  793. package/src/lib/mst/index.d.ts +19 -0
  794. package/src/lib/mst/index.js +20 -0
  795. package/src/lib/mst/index.js.map +1 -0
  796. package/src/lib/mst/json.d.ts +22 -0
  797. package/src/lib/mst/json.js +23 -0
  798. package/src/lib/mst/json.js.map +1 -0
  799. package/src/lib/mst/types.d.ts +20 -0
  800. package/src/lib/mst/types.js +18 -0
  801. package/src/lib/mst/types.js.map +1 -0
  802. package/src/lib/plugin-manager/billing-webhook-hooks.d.ts +77 -0
  803. package/src/lib/plugin-manager/billing-webhook-hooks.js +55 -0
  804. package/src/lib/plugin-manager/billing-webhook-hooks.js.map +1 -0
  805. package/src/lib/plugin-manager/commerce-zone-props.d.ts +172 -0
  806. package/src/lib/plugin-manager/commerce-zone-props.js +31 -0
  807. package/src/lib/plugin-manager/commerce-zone-props.js.map +1 -0
  808. package/src/lib/plugin-manager/custom-fields.d.ts +60 -0
  809. package/src/lib/plugin-manager/custom-fields.js +39 -0
  810. package/src/lib/plugin-manager/custom-fields.js.map +1 -0
  811. package/src/lib/plugin-manager/editor-sessions.d.ts +109 -0
  812. package/src/lib/plugin-manager/editor-sessions.js +137 -0
  813. package/src/lib/plugin-manager/editor-sessions.js.map +1 -0
  814. package/src/lib/plugin-manager/enabled-plugins.d.ts +438 -0
  815. package/src/lib/plugin-manager/enabled-plugins.js +403 -0
  816. package/src/lib/plugin-manager/enabled-plugins.js.map +1 -0
  817. package/src/lib/plugin-manager/feature-plugins.d.ts +1314 -0
  818. package/src/lib/plugin-manager/feature-plugins.js +609 -0
  819. package/src/lib/plugin-manager/feature-plugins.js.map +1 -0
  820. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +11 -0
  821. package/src/lib/plugin-manager/first-party-plugins.generated.js +149 -0
  822. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -0
  823. package/src/lib/plugin-manager/index.d.ts +42 -0
  824. package/src/lib/plugin-manager/index.js +46 -0
  825. package/src/lib/plugin-manager/index.js.map +1 -0
  826. package/src/lib/plugin-manager/install-presets.d.ts +93 -0
  827. package/src/lib/plugin-manager/install-presets.js +86 -0
  828. package/src/lib/plugin-manager/install-presets.js.map +1 -0
  829. package/src/lib/plugin-manager/media-delivery-provider.d.ts +124 -0
  830. package/src/lib/plugin-manager/media-delivery-provider.js +53 -0
  831. package/src/lib/plugin-manager/media-delivery-provider.js.map +1 -0
  832. package/src/lib/plugin-manager/order-fulfilment.d.ts +142 -0
  833. package/src/lib/plugin-manager/order-fulfilment.js +104 -0
  834. package/src/lib/plugin-manager/order-fulfilment.js.map +1 -0
  835. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +122 -0
  836. package/src/lib/plugin-manager/plugin-activity-actions.js +140 -0
  837. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -0
  838. package/src/lib/plugin-manager/plugin-bridge.d.ts +98 -0
  839. package/src/lib/plugin-manager/plugin-bridge.js +154 -0
  840. package/src/lib/plugin-manager/plugin-bridge.js.map +1 -0
  841. package/src/lib/plugin-manager/plugin-config.d.ts +148 -0
  842. package/src/lib/plugin-manager/plugin-config.js +228 -0
  843. package/src/lib/plugin-manager/plugin-config.js.map +1 -0
  844. package/src/lib/plugin-manager/plugin-console-crons.d.ts +139 -0
  845. package/src/lib/plugin-manager/plugin-console-crons.js +177 -0
  846. package/src/lib/plugin-manager/plugin-console-crons.js.map +1 -0
  847. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +220 -0
  848. package/src/lib/plugin-manager/plugin-contact-capture.js +89 -0
  849. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -0
  850. package/src/lib/plugin-manager/plugin-contributions.d.ts +244 -0
  851. package/src/lib/plugin-manager/plugin-contributions.js +283 -0
  852. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -0
  853. package/src/lib/plugin-manager/plugin-entitlement-keys.d.ts +169 -0
  854. package/src/lib/plugin-manager/plugin-entitlement-keys.js +84 -0
  855. package/src/lib/plugin-manager/plugin-entitlement-keys.js.map +1 -0
  856. package/src/lib/plugin-manager/plugin-entitlements.d.ts +181 -0
  857. package/src/lib/plugin-manager/plugin-entitlements.js +188 -0
  858. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -0
  859. package/src/lib/plugin-manager/plugin-events.d.ts +145 -0
  860. package/src/lib/plugin-manager/plugin-events.js +79 -0
  861. package/src/lib/plugin-manager/plugin-events.js.map +1 -0
  862. package/src/lib/plugin-manager/plugin-figures.d.ts +251 -0
  863. package/src/lib/plugin-manager/plugin-figures.js +252 -0
  864. package/src/lib/plugin-manager/plugin-figures.js.map +1 -0
  865. package/src/lib/plugin-manager/plugin-host-collections.d.ts +146 -0
  866. package/src/lib/plugin-manager/plugin-host-collections.js +116 -0
  867. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -0
  868. package/src/lib/plugin-manager/plugin-jobs.d.ts +130 -0
  869. package/src/lib/plugin-manager/plugin-jobs.js +182 -0
  870. package/src/lib/plugin-manager/plugin-jobs.js.map +1 -0
  871. package/src/lib/plugin-manager/plugin-loader.d.ts +86 -0
  872. package/src/lib/plugin-manager/plugin-loader.js +223 -0
  873. package/src/lib/plugin-manager/plugin-loader.js.map +1 -0
  874. package/src/lib/plugin-manager/plugin-manager.d.ts +95 -0
  875. package/src/lib/plugin-manager/plugin-manager.js +343 -0
  876. package/src/lib/plugin-manager/plugin-manager.js.map +1 -0
  877. package/src/lib/plugin-manager/plugin-metered-lines.d.ts +97 -0
  878. package/src/lib/plugin-manager/plugin-metered-lines.js +123 -0
  879. package/src/lib/plugin-manager/plugin-metered-lines.js.map +1 -0
  880. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +52 -0
  881. package/src/lib/plugin-manager/plugin-org-erasure.js +100 -0
  882. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -0
  883. package/src/lib/plugin-manager/plugin-permissions.d.ts +69 -0
  884. package/src/lib/plugin-manager/plugin-permissions.js +77 -0
  885. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -0
  886. package/src/lib/plugin-manager/plugin-person-erasure.d.ts +58 -0
  887. package/src/lib/plugin-manager/plugin-person-erasure.js +109 -0
  888. package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -0
  889. package/src/lib/plugin-manager/plugin-record-cards.d.ts +99 -0
  890. package/src/lib/plugin-manager/plugin-record-cards.js +59 -0
  891. package/src/lib/plugin-manager/plugin-record-cards.js.map +1 -0
  892. package/src/lib/plugin-manager/plugin-record-facts.d.ts +111 -0
  893. package/src/lib/plugin-manager/plugin-record-facts.js +51 -0
  894. package/src/lib/plugin-manager/plugin-record-facts.js.map +1 -0
  895. package/src/lib/plugin-manager/plugin-record-routes.d.ts +129 -0
  896. package/src/lib/plugin-manager/plugin-record-routes.js +93 -0
  897. package/src/lib/plugin-manager/plugin-record-routes.js.map +1 -0
  898. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +147 -0
  899. package/src/lib/plugin-manager/plugin-record-timeline.js +39 -0
  900. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -0
  901. package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +137 -0
  902. package/src/lib/plugin-manager/plugin-resource-drafts.js +51 -0
  903. package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -0
  904. package/src/lib/plugin-manager/plugin-services.d.ts +94 -0
  905. package/src/lib/plugin-manager/plugin-services.js +153 -0
  906. package/src/lib/plugin-manager/plugin-services.js.map +1 -0
  907. package/src/lib/plugin-manager/plugin-styles-ui.d.ts +71 -0
  908. package/src/lib/plugin-manager/plugin-styles-ui.js +80 -0
  909. package/src/lib/plugin-manager/plugin-styles-ui.js.map +1 -0
  910. package/src/lib/plugin-manager/plugin-styles.d.ts +176 -0
  911. package/src/lib/plugin-manager/plugin-styles.js +186 -0
  912. package/src/lib/plugin-manager/plugin-styles.js.map +1 -0
  913. package/src/lib/plugin-manager/plugin-subprocessors.d.ts +135 -0
  914. package/src/lib/plugin-manager/plugin-subprocessors.js +129 -0
  915. package/src/lib/plugin-manager/plugin-subprocessors.js.map +1 -0
  916. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +84 -0
  917. package/src/lib/plugin-manager/plugin-tax-profile.js +39 -0
  918. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -0
  919. package/src/lib/plugin-manager/plugin-user-erasure.d.ts +53 -0
  920. package/src/lib/plugin-manager/plugin-user-erasure.js +84 -0
  921. package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -0
  922. package/src/lib/plugin-manager/plugin-zones.d.ts +121 -0
  923. package/src/lib/plugin-manager/plugin-zones.js +74 -0
  924. package/src/lib/plugin-manager/plugin-zones.js.map +1 -0
  925. package/src/lib/plugin-manager/realm-plugins.d.ts +112 -0
  926. package/src/lib/plugin-manager/realm-plugins.js +196 -0
  927. package/src/lib/plugin-manager/realm-plugins.js.map +1 -0
  928. package/src/lib/plugin-manager/realm-server.d.ts +54 -0
  929. package/src/lib/plugin-manager/realm-server.js +88 -0
  930. package/src/lib/plugin-manager/realm-server.js.map +1 -0
  931. package/src/lib/plugin-manager/record-zone-props.d.ts +126 -0
  932. package/src/lib/plugin-manager/record-zone-props.js +31 -0
  933. package/src/lib/plugin-manager/record-zone-props.js.map +1 -0
  934. package/src/lib/plugin-manager/site-page-hooks.d.ts +102 -0
  935. package/src/lib/plugin-manager/site-page-hooks.js +114 -0
  936. package/src/lib/plugin-manager/site-page-hooks.js.map +1 -0
  937. package/src/lib/plugin-manager/site-runtime.d.ts +57 -0
  938. package/src/lib/plugin-manager/site-runtime.js +25 -0
  939. package/src/lib/plugin-manager/site-runtime.js.map +1 -0
  940. package/src/lib/plugin-manager/usage-alert-contributors.d.ts +115 -0
  941. package/src/lib/plugin-manager/usage-alert-contributors.js +106 -0
  942. package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -0
  943. package/src/lib/types/ast.d.ts +199 -0
  944. package/src/lib/types/ast.js +20 -0
  945. package/src/lib/types/ast.js.map +1 -0
  946. package/src/lib/types/index.d.ts +20 -0
  947. package/src/lib/types/index.js +21 -0
  948. package/src/lib/types/index.js.map +1 -0
  949. package/src/lib/types/nodes.d.ts +608 -0
  950. package/src/lib/types/nodes.js +28 -0
  951. package/src/lib/types/nodes.js.map +1 -0
  952. package/src/lib/types/routing.d.ts +17 -0
  953. package/src/lib/types/routing.js +18 -0
  954. package/src/lib/types/routing.js.map +1 -0
  955. package/src/lib/types/screen.d.ts +23 -0
  956. package/src/lib/types/screen.js +18 -0
  957. package/src/lib/types/screen.js.map +1 -0
  958. package/src/lib/utils.d.ts +25 -0
  959. package/src/lib/utils.js +42 -0
  960. package/src/lib/utils.js.map +1 -0
  961. package/src/server.d.ts +37 -0
  962. package/src/server.js +55 -0
  963. package/src/server.js.map +1 -0
@@ -0,0 +1,2072 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Shared shape for the `/api/health` endpoints (AGL-1102).
18
+ *
19
+ * Both the console and the tenant runtime answer this, and they must answer it
20
+ * IDENTICALLY — an uptime series is only comparable across services if the
21
+ * status codes and the field names agree. Two near-identical route files would
22
+ * have drifted the first time one of them was touched.
23
+ *
24
+ * The rules encoded here, each of which is a way health checks routinely lie:
25
+ *
26
+ * **The status code is the contract.** Most uptime monitors read nothing else,
27
+ * so a degraded dependency has to be a 503. A 200 whose body says "degraded"
28
+ * is a signal nobody receives.
29
+ *
30
+ * **Never cacheable.** A cached health check returns 200 from the edge while
31
+ * the origin is on fire, and the graph stays green straight through the
32
+ * outage. Three separate caches have faked a green check in this repo already,
33
+ * so this sets `no-store` on every response including the failures — an error
34
+ * response is the one you least want served from a cache.
35
+ *
36
+ * **No internals in the body.** It is public and unauthenticated, so a failing
37
+ * check reports a CODE and never the underlying message, which can carry
38
+ * project ids, paths or credentials fragments.
39
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
40
+ export const HEALTH_NO_STORE = 'no-store, no-cache, must-revalidate, max-age=0';
41
+ /** Degraded if ANY check failed. There is no partial credit for uptime. */ export function healthStatus(checks) {
42
+ return Object.values(checks).every((check)=>check.ok) ? 'ok' : 'degraded';
43
+ }
44
+ export function healthBody(report) {
45
+ var _report_commit, _report_version, _report_environment, _report_region, _report_at;
46
+ return {
47
+ status: healthStatus(report.checks),
48
+ service: report.service,
49
+ checks: report.checks,
50
+ commit: (_report_commit = report.commit) != null ? _report_commit : null,
51
+ version: (_report_version = report.version) != null ? _report_version : null,
52
+ environment: (_report_environment = report.environment) != null ? _report_environment : null,
53
+ region: (_report_region = report.region) != null ? _report_region : null,
54
+ at: (_report_at = report.at) != null ? _report_at : new Date().toISOString()
55
+ };
56
+ }
57
+ export function healthHeaders(status) {
58
+ return _extends({
59
+ 'Cache-Control': HEALTH_NO_STORE,
60
+ // Readable from any origin, deliberately.
61
+ //
62
+ // A status page must not live on the thing it reports on, so it is served
63
+ // from a DIFFERENT deployment and reads these cross-origin. Without this
64
+ // header the browser blocks the read and the page renders every service as
65
+ // down during a perfectly healthy day — a status page that cries outage is
66
+ // worse than none.
67
+ //
68
+ // Safe to open: the body is already public and unauthenticated, carries no
69
+ // secrets, and a failing check reports a code rather than a message.
70
+ 'Access-Control-Allow-Origin': '*'
71
+ }, status === 'ok' ? {} : {
72
+ 'Retry-After': '30'
73
+ });
74
+ }
75
+ export function healthHttpStatus(status) {
76
+ return status === 'ok' ? 200 : 503;
77
+ }
78
+ /**
79
+ * A HEAD response for a health endpoint: the SAME status and headers its GET
80
+ * would return, with no body (AGL-1148).
81
+ *
82
+ * ## Why this exists
83
+ *
84
+ * Every health route used to answer HEAD with a hardcoded `200` and the
85
+ * comment "cheap liveness for monitors that only issue HEAD. Touches
86
+ * nothing." Touching nothing is precisely the problem: **a HEAD that knows
87
+ * nothing is a health check that cannot go red.** A monitor configured with
88
+ * HEAD — which several uptime providers use by default — would have reported
89
+ * green through any outage that left the function able to boot, which is most
90
+ * of them. `/api/health/crons` sat at 503 for fifty-one hours; a HEAD monitor
91
+ * pointed at it would have agreed with the green board the whole time.
92
+ *
93
+ * It also violated the contract this file opens with. "The status code is the
94
+ * contract" cannot hold for one method and not another, and HTTP requires a
95
+ * HEAD response to carry the same status and headers its GET would.
96
+ *
97
+ * ## The cost argument, which is why this is safe
98
+ *
99
+ * Delegating to GET means HEAD runs the dependency probe. Every route
100
+ * memoizes that probe (`memoizeWithTtl`, 15s on the root and 5 minutes on the
101
+ * subsystems) explicitly so a public unauthenticated endpoint cannot be made
102
+ * to read in a loop — the same bound that already lets a fifteen-minute probe
103
+ * hit seven endpoints for nearly nothing. HEAD now costs exactly what GET
104
+ * costs, which is a memo lookup between refreshes, and it serializes no body.
105
+ *
106
+ * Headers are carried over rather than rebuilt so `Cache-Control: no-store`,
107
+ * the open CORS origin and `Retry-After` reach a HEAD client identically — a
108
+ * HEAD response that was cacheable while its GET was not would be the same
109
+ * lie one layer down.
110
+ */ export async function healthHeadOf(get) {
111
+ const response = await get();
112
+ return new Response(null, {
113
+ status: response.status,
114
+ headers: response.headers
115
+ });
116
+ }
117
+ /**
118
+ * Weekly Sunday schedule + one day of slack. A healthy cadence never exceeds
119
+ * 7; day 8 means the last run produced nothing usable.
120
+ */ export const MAX_BACKUP_AGE_DAYS = 8;
121
+ /**
122
+ * States in which a backup has not FAILED — it is simply not usable yet.
123
+ *
124
+ * `STATE_UNSPECIFIED` is a real member of the API's `Backup.State` enum ("The
125
+ * state is unspecified"), and proto3 JSON omits a default-valued field, so a
126
+ * backup in it arrives with **no `state` key at all**. Treating that absence
127
+ * as a failure is the `strictNullChecks`-off trap this file is otherwise
128
+ * careful about: a missing field must never fold into a red.
129
+ */ const BACKUP_IN_FLIGHT = new Set([
130
+ 'CREATING',
131
+ 'STATE_UNSPECIFIED'
132
+ ]);
133
+ /**
134
+ * `backups` is the listing, or `null` when it could not be read at all.
135
+ *
136
+ * `options.unreachable` is `ListBackupsResponse.unreachable` — the locations
137
+ * the API could not reach. Non-empty means the listing is a PARTIAL result
138
+ * set. `options.code` is the code to report when `backups` is `null`.
139
+ */ export function backupsHealth(backups, ms, now = Date.now(), options = {}) {
140
+ var _options_unreachable;
141
+ // ## THE THREE STATES (AGL-1843, second pass)
142
+ //
143
+ // This check used to have two: healthy, and failed. Everything it could not
144
+ // establish — an upstream that errored, a partial listing, a run that had
145
+ // not finished — collapsed into "backup failed", which is the loudest thing
146
+ // it can say and was, in every one of those cases, false. It reported 503
147
+ // `backup-failed` continuously for four and a half days while the backups
148
+ // were fine, against a monitor that emails on a five-minute interval.
149
+ //
150
+ // ### Why `NOT_AVAILABLE` is not a failure — measured, not assumed
151
+ //
152
+ // The API documents the state as "The backup is not available **at this
153
+ // moment**", and that temporal wording is literal. The same three backup
154
+ // ids, read from `gcloud firestore backups list` on three dates:
155
+ //
156
+ // id 2026-08-13 2026-08-17/18 2026-08-24
157
+ // 3b5238df… NOT_AVAILABLE NOT_AVAILABLE READY
158
+ // eb4d21e3… READY NOT_AVAILABLE READY
159
+ // d14ce827… — READY READY
160
+ //
161
+ // `NOT_AVAILABLE` flips in BOTH directions, on backups whose `expireTime`
162
+ // is months away, independent of age. The 2026-08-02 backup that AGL-1490
163
+ // was filed about — "half the Firestore backups are NOT_AVAILABLE" — is
164
+ // READY today, twenty-two days old. It was never broken. Neither was the
165
+ // 2026-08-09 one, which went READY → NOT_AVAILABLE → READY. And the
166
+ // predicted permanent day-7 degradation did not happen: d14ce827 is READY
167
+ // at day 8. So every rule that reads a `NOT_AVAILABLE` count as damage is
168
+ // reading a maintenance window as an outage.
169
+ //
170
+ // ### What the check actually needs to answer
171
+ //
172
+ // "Can we restore, and is the restore point recent?" That question is
173
+ // answered ENTIRELY by the age of the newest READY backup. No other row in
174
+ // the listing can make a fresh READY backup un-restorable. So a fresh READY
175
+ // backup is unconditionally green, and everything below it is the taxonomy
176
+ // of why we could not find one.
177
+ //
178
+ // ### Why indeterminate answers 200, and why that is not fail-open
179
+ //
180
+ // Three things bound it, because a backups check that cannot go red is
181
+ // worse than no backups check:
182
+ // 1. It is time-bounded. Indeterminacy is only tolerated while a backup
183
+ // younger than `MAX_BACKUP_AGE_DAYS` exists. A schedule that stops, or
184
+ // that never produces anything usable, crosses the budget and goes red
185
+ // within eight days whatever the states say.
186
+ // 2. It never overrides staleness. If a READY backup exists and is over
187
+ // budget, that is `backup-stale` — a newer CREATING or NOT_AVAILABLE
188
+ // row earns no freshness credit, so a run that hangs every week cannot
189
+ // hold the check green.
190
+ // 3. The route classifies PERMANENT upstream failures (no credential,
191
+ // 401/403/404) as hard red rather than indeterminate, so a revoked IAM
192
+ // role cannot silence this forever. And `checks.exports` in the same
193
+ // endpoint is an independent, still-hard-red probe of the GCS copy:
194
+ // "we cannot read the managed backups" answers 200 only while we can
195
+ // still prove a fresh independent copy exists.
196
+ //
197
+ // State 3a — the listing could not be read AT ALL. That is not a verdict;
198
+ // it is the absence of one, and reporting it as `backup-failed` is the
199
+ // defect this pass exists to remove.
200
+ if (backups === null) {
201
+ var _options_code;
202
+ return {
203
+ ok: true,
204
+ ms,
205
+ code: (_options_code = options.code) != null ? _options_code : 'backups-unreadable',
206
+ states: {},
207
+ newestReadyAgeDays: null,
208
+ determinate: false
209
+ };
210
+ }
211
+ const states = {};
212
+ for (const backup of backups){
213
+ var _backup_state, _states_state;
214
+ const state = (_backup_state = backup.state) != null ? _backup_state : 'STATE_UNSPECIFIED';
215
+ states[state] = ((_states_state = states[state]) != null ? _states_state : 0) + 1;
216
+ }
217
+ const snapshotMsOf = (backup)=>backup.snapshotTime ? Date.parse(backup.snapshotTime) : Number.NaN;
218
+ const newestOf = (keep)=>backups.filter(keep).map(snapshotMsOf).filter((time)=>Number.isFinite(time)).reduce((newest, time)=>Math.max(newest != null ? newest : time, time), null);
219
+ const ageDaysOf = (time)=>Math.round((now - time) / 86400000 * 10) / 10;
220
+ const newestReadyMs = newestOf((backup)=>backup.state === 'READY');
221
+ const newestAnyMs = newestOf(()=>true);
222
+ const newestReadyAgeDays = newestReadyMs === null ? null : ageDaysOf(newestReadyMs);
223
+ const newestAnyAgeDays = newestAnyMs === null ? null : ageDaysOf(newestAnyMs);
224
+ // State 1 — HEALTHY. A restore point exists and it is recent. Nothing else
225
+ // in the listing can falsify that, so nothing else is consulted.
226
+ if (newestReadyAgeDays !== null && newestReadyAgeDays <= MAX_BACKUP_AGE_DAYS) {
227
+ return {
228
+ ok: true,
229
+ ms,
230
+ states,
231
+ newestReadyAgeDays
232
+ };
233
+ }
234
+ // State 2a — FAILED. A backup that is neither READY nor in flight and that
235
+ // carries no usable `snapshotTime` cannot be aged at all, so we cannot even
236
+ // say when the last run was. That is positive evidence of something wrong,
237
+ // and "assume it aged out" is the assumption that hides a broken run.
238
+ const undateable = backups.some((backup)=>{
239
+ var _backup_state;
240
+ return backup.state !== 'READY' && !BACKUP_IN_FLIGHT.has((_backup_state = backup.state) != null ? _backup_state : 'STATE_UNSPECIFIED') && !Number.isFinite(snapshotMsOf(backup));
241
+ });
242
+ if (undateable) {
243
+ return {
244
+ ok: false,
245
+ ms,
246
+ code: 'backup-failed',
247
+ states,
248
+ newestReadyAgeDays
249
+ };
250
+ }
251
+ // State 3b — the listing is a PARTIAL result set. `ListBackupsResponse`
252
+ // carries `unreachable`: rather than failing the request when one location
253
+ // cannot be reached, the API returns the backups it could see and names the
254
+ // ones it could not. Positive evidence survives that (state 1 above already
255
+ // returned); negative evidence does not — "we saw no fresh READY backup" is
256
+ // not a finding when we did not see everything.
257
+ if (((_options_unreachable = options.unreachable) != null ? _options_unreachable : []).length > 0) {
258
+ return {
259
+ ok: true,
260
+ ms,
261
+ code: 'backups-partial',
262
+ states,
263
+ newestReadyAgeDays,
264
+ determinate: false
265
+ };
266
+ }
267
+ // State 3c — a recent run exists, it simply is not usable AT THIS MOMENT:
268
+ // mid-creation on a Sunday, or inside one of the `NOT_AVAILABLE` windows
269
+ // measured above. Only reachable when NOTHING is READY — a stale READY
270
+ // backup falls through to `backup-stale` below, so this can never buy a
271
+ // hanging schedule more than the eight-day budget.
272
+ if (newestReadyAgeDays === null && newestAnyAgeDays !== null && newestAnyAgeDays <= MAX_BACKUP_AGE_DAYS) {
273
+ return {
274
+ ok: true,
275
+ ms,
276
+ code: 'backups-not-ready-yet',
277
+ states,
278
+ newestReadyAgeDays,
279
+ determinate: false
280
+ };
281
+ }
282
+ // State 2b — FAILED on freshness. Either nothing has ever been READY and
283
+ // nothing recent exists to be waiting on, or the newest READY backup is
284
+ // past the budget. Both mean the same operational thing: there is no recent
285
+ // restore point, which is the only fact this check exists to protect.
286
+ return {
287
+ ok: false,
288
+ ms,
289
+ code: newestReadyAgeDays === null ? 'no-ready-backup' : 'backup-stale',
290
+ states,
291
+ newestReadyAgeDays
292
+ };
293
+ }
294
+ /**
295
+ * Weekly cadence (Mondays, `scheduled-crons.yml`) + one day of slack — the
296
+ * same budget as `MAX_BACKUP_AGE_DAYS`, for the same reason: a schedule that
297
+ * stops producing is as broken as one that fails.
298
+ */ export const MAX_EXPORT_AGE_DAYS = 8;
299
+ export function exportsHealth(markers, ms, now = Date.now()) {
300
+ // A failed listing is degraded, not ok — same rule as `signupsHealth` and
301
+ // `rateLimitsHealth`: an alarm that cannot see the thing it watches must
302
+ // say so rather than report calm.
303
+ if (markers === null) {
304
+ return {
305
+ ok: false,
306
+ ms,
307
+ code: 'exports-unavailable',
308
+ exportCount: null,
309
+ newestExportAgeDays: null
310
+ };
311
+ }
312
+ const newestMs = markers.map((marker)=>{
313
+ var _marker_timeCreated;
314
+ return Date.parse((_marker_timeCreated = marker.timeCreated) != null ? _marker_timeCreated : '');
315
+ }).filter((time)=>Number.isFinite(time)).reduce((newest, time)=>Math.max(newest != null ? newest : time, time), null);
316
+ const newestExportAgeDays = newestMs === null ? null : Math.round((now - newestMs) / 86400000 * 10) / 10;
317
+ // The cron exists, so "no exports" is a failure, not a fresh start — the
318
+ // first export ran 2026-08-17, before this check first deployed.
319
+ const code = newestExportAgeDays === null ? 'no-export' : newestExportAgeDays > MAX_EXPORT_AGE_DAYS ? 'export-stale' : undefined;
320
+ return _extends({
321
+ ok: code === undefined,
322
+ ms
323
+ }, code === undefined ? {} : {
324
+ code
325
+ }, {
326
+ exportCount: markers.length,
327
+ newestExportAgeDays
328
+ });
329
+ }
330
+ /** Trailing window the org-creation count covers. */ export const ORG_CREATION_WINDOW_MINUTES = 60;
331
+ /**
332
+ * Degraded above 10 creations/hour. Calibrated against two facts: production
333
+ * holds 4 orgs TOTAL (2026-08), so the organic baseline is ~zero per hour;
334
+ * and the AGL-1534 per-IP cap is 10/h, so a single maxed-out address can
335
+ * never trip this — 11+/h necessarily means multiple addresses, which is
336
+ * exactly the distributed signature the rate limiter cannot see. If a launch
337
+ * day legitimately beats this, the alert asks a human to glance at the orgs
338
+ * list and enjoy the view; that is the correct outcome, not a false positive.
339
+ */ export const MAX_ORG_CREATIONS_PER_WINDOW = 10;
340
+ export function signupsHealth(recentOrgCreations, ms, threshold = MAX_ORG_CREATIONS_PER_WINDOW) {
341
+ // A failed count is degraded, not ok — an alarm that cannot see the thing
342
+ // it watches must say so rather than report calm.
343
+ const code = recentOrgCreations === null ? 'count-unavailable' : recentOrgCreations > threshold ? 'signup-wave' : undefined;
344
+ return _extends({
345
+ ok: code === undefined,
346
+ ms
347
+ }, code === undefined ? {} : {
348
+ code
349
+ }, {
350
+ recentOrgCreations,
351
+ windowMinutes: ORG_CREATION_WINDOW_MINUTES,
352
+ threshold
353
+ });
354
+ }
355
+ /**
356
+ * Trailing window the refusal count covers. One hour, matching the limiter's
357
+ * own window (`org-create` is 3/h and 10/h) so the number is readable against
358
+ * the caps that produced it.
359
+ */ export const SIGNUP_REFUSAL_WINDOW_MINUTES = 60;
360
+ /**
361
+ * Degraded above 50 refusals/hour.
362
+ *
363
+ * Not zero, unlike the limiter-degradation alarm: a refusal is the system
364
+ * working, and a real person who fumbles a workspace slug four times in an
365
+ * hour produces one. The number that means something is *sustained* refusal.
366
+ * Calibrated the same way AGL-1536's was: production holds single-digit orgs
367
+ * (2026-08), so organic refusals are ~zero per hour, and 50 is far above any
368
+ * plausible human while being reached in under a minute by a script. One
369
+ * determined address can generate this alone — that is intended, because "one
370
+ * address is hammering the signup door" is worth an email even though the
371
+ * limiter is containing it.
372
+ */ export const MAX_SIGNUP_REFUSALS_PER_WINDOW = 50;
373
+ /**
374
+ * The marker field the blocking function counts blind decisions under
375
+ * (AGL-2583).
376
+ *
377
+ * `signupsCreationVerdict` answers `locked` when a completed read found the
378
+ * lever pulled, `held` when a failed read fell back on an earlier one that
379
+ * had, and — the case this names — admits the account when the read failed
380
+ * and nothing remembered said the lever was pulled. That last one refuses
381
+ * nobody, so it is not a `byReason` entry; it is the platform deciding the
382
+ * signups question without being able to consult it, which is the visible
383
+ * edge of a Firestore read outage on the account-creation path. AGL-2581 was
384
+ * three days of that same read failing, and its only trace was a
385
+ * `logger.warn` nothing alerts on.
386
+ */ export const SIGNUP_LOCK_UNREADABLE_FIELD = 'unreadable';
387
+ /**
388
+ * Blind signups-lock decisions tolerated per window: NONE.
389
+ *
390
+ * Deliberately not calibrated like `MAX_SIGNUP_REFUSALS_PER_WINDOW` above,
391
+ * which is a volume threshold over refusals that mean the system is working.
392
+ * A blind decision is never routine — a single one says the signups lock was
393
+ * unreadable at least once, and the lock is unenforceable for as long as that
394
+ * lasts. False positives here cost a glance at one endpoint.
395
+ */ export const MAX_UNREADABLE_SIGNUP_DECISIONS_PER_WINDOW = 0;
396
+ export function signupRefusalsHealth(markers, ms, now = Date.now(), threshold = MAX_SIGNUP_REFUSALS_PER_WINDOW, windowMinutes = SIGNUP_REFUSAL_WINDOW_MINUTES, unreadableThreshold = MAX_UNREADABLE_SIGNUP_DECISIONS_PER_WINDOW) {
397
+ // A failed query is degraded, not ok — the same rule `signupsHealth` and
398
+ // `rateLimitsHealth` follow. An alarm that cannot see the thing it watches
399
+ // must not report calm.
400
+ if (markers === null) {
401
+ return {
402
+ ok: false,
403
+ ms,
404
+ code: 'refusals-unavailable',
405
+ refusedSignups: null,
406
+ refusedByReason: null,
407
+ lockUnreadable: null,
408
+ minutesSinceLast: null,
409
+ windowMinutes,
410
+ threshold
411
+ };
412
+ }
413
+ let refusedSignups = 0;
414
+ let lockUnreadable = 0;
415
+ let newestAtMs = null;
416
+ const refusedByReason = {};
417
+ for (const marker of markers){
418
+ var _marker_refusals, _marker_byReason;
419
+ refusedSignups += (_marker_refusals = marker.refusals) != null ? _marker_refusals : 0;
420
+ // `Number(...) || 0` for the reason the split below gives: a corrupt
421
+ // Firestore field must not turn this into NaN, which compares false
422
+ // against every threshold and would report calm forever.
423
+ lockUnreadable += Number(marker.unreadable) || 0;
424
+ for (const [reason, count] of Object.entries((_marker_byReason = marker.byReason) != null ? _marker_byReason : {})){
425
+ var _refusedByReason_reason;
426
+ // `Number(...) || 0` rather than a bare add: these come out of Firestore
427
+ // and a corrupt field must not turn the total into NaN, which compares
428
+ // false against the threshold and would report calm forever.
429
+ refusedByReason[reason] = ((_refusedByReason_reason = refusedByReason[reason]) != null ? _refusedByReason_reason : 0) + (Number(count) || 0);
430
+ }
431
+ const at = marker.refusedAtMs;
432
+ if (typeof at === 'number' && (newestAtMs === null || at > newestAtMs)) {
433
+ newestAtMs = at;
434
+ }
435
+ }
436
+ const over = refusedSignups > threshold;
437
+ // The blind count is graded FIRST and reported over the volume code. A wave
438
+ // and an unreadable lock call for opposite responses — leave the limiter
439
+ // alone and go read the refusal reasons, versus go find out why Firestore
440
+ // is unreadable from the account-creation path — so the code has to name
441
+ // the one that is actually happening.
442
+ const blind = lockUnreadable > unreadableThreshold;
443
+ const code = blind ? 'signups-lock-unreadable' : over ? 'signup-refusal-wave' : undefined;
444
+ return _extends({
445
+ ok: code === undefined,
446
+ ms
447
+ }, code === undefined ? {} : {
448
+ code
449
+ }, {
450
+ refusedSignups,
451
+ refusedByReason,
452
+ lockUnreadable,
453
+ minutesSinceLast: newestAtMs === null ? null : Math.max(0, Math.floor((now - newestAtMs) / 60000)),
454
+ windowMinutes,
455
+ threshold
456
+ });
457
+ }
458
+ /**
459
+ * Trailing window the drought verdict covers. One hour, matching the sibling
460
+ * checks so the three numbers on this endpoint are all read against the same
461
+ * clock — and long enough that a single slow afternoon does not page anyone.
462
+ */ export const SIGNUP_DROUGHT_WINDOW_MINUTES = 60;
463
+ /**
464
+ * Attempts in the window below which zero accounts means nothing.
465
+ *
466
+ * Three, against attempts rather than the five it once meant against serves
467
+ * (AGL-2714). The unit changed, so the number had to be re-argued rather than
468
+ * carried over.
469
+ *
470
+ * Not one: a browser can lose a request, and a person can start the form and
471
+ * abandon it between authenticating and the org write. Not ten either — at a
472
+ * handful of signups a week, waiting for ten attempts to fail would mean
473
+ * waiting days to notice a door that stopped opening, which is the failure
474
+ * this check exists to catch inside an hour.
475
+ *
476
+ * Three distinct requests to create an org, none of which produced one and
477
+ * none of which was refused, is a door that does not open. That reading has
478
+ * no quiet-hour interpretation: nobody attempts three times by browsing.
479
+ */ export const MIN_SIGNUP_TRAFFIC_FOR_DROUGHT = 3;
480
+ export function signupDroughtHealth(markers, attemptMarkers, orgCreations, ms, minimumTraffic = MIN_SIGNUP_TRAFFIC_FOR_DROUGHT, windowMinutes = SIGNUP_DROUGHT_WINDOW_MINUTES) {
481
+ // Serves are context, not the verdict, so they are summed wherever they are
482
+ // readable and reported as null when they are not — a missing context line
483
+ // must not decide anything on its own.
484
+ let signupPagesServed = null;
485
+ if (markers !== null) {
486
+ signupPagesServed = 0;
487
+ for (const marker of markers){
488
+ // `Number(...) || 0` for the reason the sibling gives: a corrupt
489
+ // Firestore field must not turn the total into NaN, which compares
490
+ // false against every threshold and would report calm forever.
491
+ signupPagesServed += Number(marker.serves) || 0;
492
+ }
493
+ }
494
+ // A failed ATTEMPT query is degraded, not ok — the same rule the siblings
495
+ // follow. Without the denominator this check has no opinion at all, and a
496
+ // check with no opinion must not spend it saying everything is fine.
497
+ if (attemptMarkers === null) {
498
+ return {
499
+ ok: false,
500
+ ms,
501
+ code: 'traffic-unavailable',
502
+ signupPagesServed,
503
+ signupAttempts: null,
504
+ orgCreations,
505
+ windowMinutes,
506
+ minimumTraffic
507
+ };
508
+ }
509
+ let signupAttempts = 0;
510
+ for (const marker of attemptMarkers){
511
+ signupAttempts += Number(marker.attempts) || 0;
512
+ }
513
+ if (orgCreations === null) {
514
+ return {
515
+ ok: false,
516
+ ms,
517
+ code: 'count-unavailable',
518
+ signupPagesServed,
519
+ signupAttempts,
520
+ orgCreations: null,
521
+ windowMinutes,
522
+ minimumTraffic
523
+ };
524
+ }
525
+ // The whole verdict: people ASKED for an org and NOT ONE of them got it.
526
+ const drought = signupAttempts >= minimumTraffic && orgCreations === 0;
527
+ return _extends({
528
+ ok: !drought,
529
+ ms
530
+ }, drought ? {
531
+ code: 'signup-drought'
532
+ } : {}, {
533
+ signupPagesServed,
534
+ signupAttempts,
535
+ orgCreations,
536
+ windowMinutes,
537
+ minimumTraffic
538
+ });
539
+ }
540
+ /**
541
+ * Trailing window the degradation counts cover.
542
+ *
543
+ * Bounded from below by the alert path, not by taste. The probe memoizes for
544
+ * `HEALTH_PROBE_TTL_MS` (2 minutes), the slowest check on this door runs
545
+ * every 15, and the alert policy wants a minute of sustained failure — so a
546
+ * window shorter than ~18 minutes can go red and green again before that
547
+ * check tells anyone, which is a check that reports nothing while looking
548
+ * like it works. 30 leaves margin and still clears itself inside half an hour.
549
+ */ export const RATE_LIMIT_DEGRADED_WINDOW_MINUTES = 30;
550
+ /**
551
+ * Any fallback at all is worth an email. Unlike the signup alarm there is no
552
+ * organic baseline to clear: a healthy deployment produces exactly zero of
553
+ * these, and one is already the statement "for some requests, the global cap
554
+ * was not the cap". Firestore retries transactions internally, so a failure
555
+ * that reaches this code is not a single unlucky contention.
556
+ */ export const MAX_DEGRADED_CALLS_PER_WINDOW = 0;
557
+ export function rateLimitsHealth(markers, ms, now = Date.now(), threshold = MAX_DEGRADED_CALLS_PER_WINDOW, windowMinutes = RATE_LIMIT_DEGRADED_WINDOW_MINUTES) {
558
+ // A failed query is degraded, not ok — same rule as `signupsHealth`. An
559
+ // alarm that cannot see the thing it watches must not report calm, and this
560
+ // one reads the collection that just failed for the limiter.
561
+ if (markers === null) {
562
+ return {
563
+ ok: false,
564
+ ms,
565
+ code: 'markers-unavailable',
566
+ degradedCalls: null,
567
+ degradedEpisodes: null,
568
+ minutesSinceLast: null,
569
+ windowMinutes,
570
+ threshold
571
+ };
572
+ }
573
+ const cutoff = now - windowMinutes * 60000;
574
+ // The window is re-applied here rather than trusted from the query, so the
575
+ // rule lives in one spec-covered place and a marker whose `lastAtMs` is
576
+ // missing cannot silently count as recent.
577
+ const recent = markers.filter((marker)=>typeof marker.lastAtMs === 'number' && marker.lastAtMs >= cutoff);
578
+ const degradedCalls = recent.reduce((total, marker)=>total + (Number(marker.calls) || 0), 0);
579
+ const degradedEpisodes = recent.reduce((total, marker)=>total + (Number(marker.episodes) || 0), 0);
580
+ const lastAtMs = recent.reduce((latest, marker)=>Math.max(latest != null ? latest : -Infinity, marker.lastAtMs), null);
581
+ return _extends({
582
+ ok: degradedCalls <= threshold,
583
+ ms
584
+ }, degradedCalls <= threshold ? {} : {
585
+ code: 'rate-limiter-degraded'
586
+ }, {
587
+ degradedCalls,
588
+ degradedEpisodes,
589
+ minutesSinceLast: lastAtMs === null ? null : Math.round((now - lastAtMs) / 60000 * 10) / 10,
590
+ windowMinutes,
591
+ threshold
592
+ });
593
+ }
594
+ /**
595
+ * Trailing window the error count covers.
596
+ *
597
+ * Bounded from below by the alert path, not by taste — the same arithmetic as
598
+ * `RATE_LIMIT_DEGRADED_WINDOW_MINUTES`: the probe memoizes for 2 minutes, the
599
+ * slowest external check runs every 15, and an alert policy wants a minute of
600
+ * sustained failure, so a window under ~18 minutes can go red and green again
601
+ * before that check tells anyone. 30 leaves margin and still clears itself
602
+ * inside half an hour of the last error.
603
+ */ export const SERVER_ERROR_WINDOW_MINUTES = 30;
604
+ /**
605
+ * Errors tolerated in the window.
606
+ *
607
+ * NOT zero, unlike the rate-limiter and billing alarms, and the difference is
608
+ * real rather than a matter of nerve. Those two watch controls that a healthy
609
+ * deployment exercises exactly never. This one watches a count that a single
610
+ * cold-start Firestore deadline, or one visitor's malformed request reaching a
611
+ * route that throws instead of 400ing, can make non-zero on a perfectly good
612
+ * day — and an alarm that pages on the first of those gets muted before the
613
+ * one that matters arrives.
614
+ *
615
+ * Five over thirty minutes is chosen against the only two facts available.
616
+ * Production serves single-digit orgs (2026-08), so the organic rate of
617
+ * UNCAUGHT server errors is ~zero — the codebase answers its expected failures
618
+ * with 4xx, and reaching this hook means nothing handled it. And the shape
619
+ * being caught is "a route is failing for real users", which does not produce
620
+ * six errors an hour; it produces six in a minute. There is deliberately no
621
+ * pretence of calibration: the beta window exists to produce a baseline, and
622
+ * `SERVER_ERROR_ALARM_MAX_ERRORS` moves this without a deploy when it does.
623
+ */ export const MAX_SERVER_ERRORS_PER_WINDOW = 5;
624
+ export function serverErrorsHealth(markers, ms, now = Date.now(), threshold = MAX_SERVER_ERRORS_PER_WINDOW, windowMinutes = SERVER_ERROR_WINDOW_MINUTES) {
625
+ // ⚠️ THE MEASURED ZERO. A failed query is degraded, never calm, and on this
626
+ // check that is not a convention borrowed from its siblings — it is the
627
+ // whole reason the check is trustworthy. "We could not count the errors"
628
+ // and "there were no errors" are the same JSON if this branch is missing,
629
+ // and the first is most likely precisely when the second is false.
630
+ if (markers === null) {
631
+ return {
632
+ ok: false,
633
+ ms,
634
+ code: 'errors-unavailable',
635
+ serverErrors: null,
636
+ byService: null,
637
+ minutesSinceLast: null,
638
+ windowMinutes,
639
+ threshold
640
+ };
641
+ }
642
+ const cutoff = now - windowMinutes * 60000;
643
+ // The window is re-applied here rather than trusted from the query, so the
644
+ // rule lives in one spec-covered place and a marker whose `erroredAtMs` is
645
+ // missing cannot silently count as recent. `strictNullChecks` is off
646
+ // repo-wide, so an absent field would otherwise compare as 0 and fall
647
+ // outside every window forever — a marker that can never be counted.
648
+ const recent = markers.filter((marker)=>typeof marker.erroredAtMs === 'number' && marker.erroredAtMs >= cutoff);
649
+ const byService = {};
650
+ let serverErrors = 0;
651
+ let lastAtMs = null;
652
+ for (const marker of recent){
653
+ var _marker_byService;
654
+ // `Number(...) || 0` rather than a bare add: these come out of Firestore
655
+ // and a corrupt field must not turn the total into NaN, which compares
656
+ // false against the threshold and would report calm forever.
657
+ serverErrors += Number(marker.errors) || 0;
658
+ for (const [service, count] of Object.entries((_marker_byService = marker.byService) != null ? _marker_byService : {})){
659
+ var _byService_service;
660
+ byService[service] = ((_byService_service = byService[service]) != null ? _byService_service : 0) + (Number(count) || 0);
661
+ }
662
+ const at = marker.erroredAtMs;
663
+ if (lastAtMs === null || at > lastAtMs) lastAtMs = at;
664
+ }
665
+ const over = serverErrors > threshold;
666
+ return _extends({
667
+ ok: !over,
668
+ ms
669
+ }, over ? {
670
+ code: 'server-error-spike'
671
+ } : {}, {
672
+ serverErrors,
673
+ byService,
674
+ minutesSinceLast: lastAtMs === null ? null : Math.max(0, Math.round((now - lastAtMs) / 60000 * 10) / 10),
675
+ windowMinutes,
676
+ threshold
677
+ });
678
+ }
679
+ /**
680
+ * How long a health door may reuse its verdict (AGL-2947).
681
+ *
682
+ * Cut from one budget: an outage must reach a person within ten minutes. A
683
+ * green verdict computed just before the onset keeps being served for this
684
+ * long, and only then can a prober see red. The fastest prober on most doors
685
+ * is UptimeRobot, every five minutes, which then confirms before it notifies.
686
+ * So the worst case is this + 5 + that confirmation: at two minutes the budget
687
+ * holds for any confirmation delay up to three. The five minutes every door
688
+ * used before spent the whole ten minutes before a notification could leave.
689
+ *
690
+ * It still bounds what a public door can be made to cost: at most one
691
+ * computation per instance per two minutes, and {@link memoizeWithTtl} shares
692
+ * a computation among the callers that arrive while it runs.
693
+ */ export const HEALTH_PROBE_TTL_MS = 2 * 60000;
694
+ /**
695
+ * Reuse a probe result for `ttlMs`, per instance.
696
+ *
697
+ * The endpoint is public and unauthenticated, so an unthrottled dependency
698
+ * check is a free amplifier: anyone could turn a loop into database reads on
699
+ * our account. This bounds the cost to one probe per instance per interval
700
+ * however hard it is hit.
701
+ *
702
+ * Callers that arrive while a probe is running share it rather than starting
703
+ * their own. The uptime check's regions, UptimeRobot and a HEAD twin routinely
704
+ * land within the same second of an expired entry, and each used to pay for a
705
+ * full probe.
706
+ *
707
+ * FAILURES ARE CACHED TOO, deliberately. A dependency that is down will be
708
+ * down for the next caller as well, and re-probing per request turns an
709
+ * outage into a stampede against the thing that is already failing. A probe
710
+ * that THROWS is not cached: its callers all see the throw, and the next
711
+ * caller probes again.
712
+ */ export function memoizeWithTtl(ttlMs, probe, now = Date.now) {
713
+ let cached = null;
714
+ let inFlight = null;
715
+ return async ()=>{
716
+ const at = now();
717
+ const elapsed = cached ? at - cached.at : Infinity;
718
+ // `elapsed >= 0` is not paranoia. A plain `elapsed < ttlMs` treats a
719
+ // BACKWARDS clock as permanently fresh — the entry never expires and the
720
+ // endpoint reports a stale verdict forever, which on a health check means
721
+ // reporting healthy through an outage. Serverless instances outlive more
722
+ // clock weirdness than a local run suggests.
723
+ if (cached && elapsed >= 0 && elapsed < ttlMs) return cached.value;
724
+ if (inFlight) return inFlight;
725
+ const flight = (async ()=>{
726
+ const value = await probe();
727
+ cached = {
728
+ at,
729
+ value
730
+ };
731
+ return value;
732
+ })();
733
+ inFlight = flight;
734
+ // Cleared once settled, and only if it is still this flight. Clearing
735
+ // inside the flight would run BEFORE the assignment above when `probe`
736
+ // throws synchronously, and leave a rejected promise in the slot forever.
737
+ const clear = ()=>{
738
+ if (inFlight === flight) inFlight = null;
739
+ };
740
+ flight.then(clear, clear);
741
+ return flight;
742
+ };
743
+ }
744
+ /**
745
+ * Codes that mean the heartbeat got no ANSWER, as opposed to a refusal
746
+ * (AGL-2713).
747
+ *
748
+ * The distinction is the whole of the tolerance below, so it is named once
749
+ * and asserted directly rather than being spelled inline at the branch.
750
+ *
751
+ * - `credential-unavailable` — the admin app is configured and its credential
752
+ * simply did not mint a token on this attempt. On a cold lambda that is the
753
+ * first outbound TLS of the instance's life, and it is the measured shape of
754
+ * both 2026-09-09 blips.
755
+ * - `transport-*` — the Logging write never came back inside its 4s deadline,
756
+ * or the fetch layer failed. Same class: nothing was decided.
757
+ *
758
+ * Everything else is a DECISION and must never be tolerated. `http-401` and
759
+ * `http-403` are a lost `logging.logEntries.create` grant, `http-429` is
760
+ * quota, and `no-credential` is a deployment whose `FIREBASE_*` env is absent
761
+ * — every one of them permanent until a person fixes it, and every one of
762
+ * them a state in which browser errors are being collected by nothing. So is
763
+ * `heartbeat-unavailable`, which means a writer documented never to throw
764
+ * threw.
765
+ */ export function isTransientBeaconCode(code) {
766
+ return code === 'credential-unavailable' || (code != null ? code : '').startsWith('transport-');
767
+ }
768
+ /**
769
+ * ## Tolerating ONE miss without becoming a door that cannot red (AGL-2713)
770
+ *
771
+ * `/api/health/error-beacon` on the console answered `503 no-credential` twice
772
+ * on 2026-09-09 — 15:12 and 17:22 UTC, each shortly after new capacity from
773
+ * the 15:08 deploy, each self-clearing within minutes, with the beacon code
774
+ * path changed by one comment line since the previous release. Between them
775
+ * they took the 15-minute uptime workflow red and opened four GCP alert
776
+ * events, on a monitor whose entire value is that somebody believes it.
777
+ *
778
+ * The tolerance is deliberately NOT "forgive a failure". It is three
779
+ * conditions that must all hold, and each one is what keeps the door able to
780
+ * go red:
781
+ *
782
+ * 1. the code says nothing was decided ({@link isTransientBeaconCode});
783
+ * 2. a heartbeat is PROVEN to have landed within `graceMs` — proof being a
784
+ * durable marker a successful write left behind, so a deployment whose
785
+ * credential never worked has nothing to forgive with and reds on its
786
+ * first probe;
787
+ * 3. that proof is not from the future, because a clock that ran backwards
788
+ * must not mint an unbounded grace.
789
+ *
790
+ * Absence of proof reds. A marker store that cannot be read reds. That is the
791
+ * same rule the `*-unavailable` arms of every sibling grader apply, pointed at
792
+ * the evidence for calm rather than at the evidence for alarm.
793
+ */ export function beaconHealth(write, logId, service, ms, tolerance = {}) {
794
+ var _write_code;
795
+ const { attempts = 1, landedAtMs = null, graceMs = 0, now = Date.now() } = tolerance;
796
+ const sinceLanded = landedAtMs === null ? null : now - landedAtMs;
797
+ const base = {
798
+ ms,
799
+ logId,
800
+ service,
801
+ attempts,
802
+ minutesSinceHeartbeat: sinceLanded === null ? null : Math.max(0, Math.round(sinceLanded / 60000 * 10) / 10),
803
+ graceMinutes: Math.round(graceMs / 60000)
804
+ };
805
+ // A null result is degraded by contract, the same rule `signupsHealth` and
806
+ // `rateLimitsHealth` follow: an alarm that cannot see the thing it watches
807
+ // must not report calm. Here it is stronger than a convention — "we could
808
+ // not determine whether the beacon works" IS the AGL-1923 condition.
809
+ if (write === null) {
810
+ return _extends({}, base, {
811
+ ok: false,
812
+ code: 'heartbeat-unavailable'
813
+ });
814
+ }
815
+ if (write.ok) return _extends({}, base, {
816
+ ok: true
817
+ });
818
+ const code = (_write_code = write.code) != null ? _write_code : 'heartbeat-failed';
819
+ if (isTransientBeaconCode(code) && sinceLanded !== null && sinceLanded >= 0 && sinceLanded <= graceMs) {
820
+ // Green, and saying so out loud. A tolerance nobody can see on the board
821
+ // is indistinguishable from a door that was widened until it stopped
822
+ // reporting, which is the failure this whole change had to avoid being.
823
+ return _extends({}, base, {
824
+ ok: true,
825
+ code: 'heartbeat-missed'
826
+ });
827
+ }
828
+ return _extends({}, base, {
829
+ ok: false,
830
+ code
831
+ });
832
+ }
833
+ export function renderHealth(outcome, host, ms) {
834
+ const base = {
835
+ ms,
836
+ host
837
+ };
838
+ switch(outcome.kind){
839
+ case 'rendered':
840
+ // Both halves are load-bearing. A host that resolved but composed
841
+ // nothing is a blank page served with a 200 — the exact outage a
842
+ // reachability ping calls healthy.
843
+ if (!outcome.hostResolved) {
844
+ return _extends({}, base, {
845
+ ok: false,
846
+ code: 'host-unresolved',
847
+ nodeCount: outcome.nodeCount
848
+ });
849
+ }
850
+ if (outcome.nodeCount <= 0) {
851
+ return _extends({}, base, {
852
+ ok: false,
853
+ code: 'rendered-empty',
854
+ nodeCount: 0
855
+ });
856
+ }
857
+ return _extends({}, base, {
858
+ ok: true,
859
+ nodeCount: outcome.nodeCount
860
+ });
861
+ case 'not-found':
862
+ return _extends({}, base, {
863
+ ok: false,
864
+ code: 'not-found',
865
+ nodeCount: 0
866
+ });
867
+ case 'redirect':
868
+ return _extends({}, base, {
869
+ ok: false,
870
+ code: 'redirected',
871
+ nodeCount: 0
872
+ });
873
+ case 'not-configured':
874
+ return _extends({}, base, {
875
+ ok: false,
876
+ code: 'not-configured',
877
+ nodeCount: 0
878
+ });
879
+ case 'unavailable':
880
+ default:
881
+ // Same rule as `beaconHealth`: "we could not determine whether the page
882
+ // renders" is a failure, never calm.
883
+ return _extends({}, base, {
884
+ ok: false,
885
+ code: 'render-unavailable',
886
+ nodeCount: 0
887
+ });
888
+ }
889
+ }
890
+ /**
891
+ * Trailing window the delivery counts cover.
892
+ *
893
+ * Bounded from below by the alert path, not by taste, exactly like
894
+ * `RATE_LIMIT_DEGRADED_WINDOW_MINUTES`: the probe memoizes for 5 minutes, the
895
+ * uptime check runs every 5, and the policy wants ~10 minutes of sustained
896
+ * failure before it emails — so a window under ~20 minutes can go red and
897
+ * green again before anyone is told. Bounded from ABOVE by Stripe's retry
898
+ * schedule: an event that fails and then succeeds on retry stops counting, so
899
+ * a window much longer than an hour would hold transient blips red past their
900
+ * own recovery.
901
+ */ export const WEBHOOK_FAILURE_WINDOW_MINUTES = 60;
902
+ /**
903
+ * Failed deliveries tolerated in the window.
904
+ *
905
+ * Zero, and unlike the signup alarm there is no organic baseline to clear: a
906
+ * healthy account produces exactly zero, and one is already the statement
907
+ * "Stripe tried to tell us something about money and could not". Stripe
908
+ * retries on its own, so a failure visible here is not one unlucky packet.
909
+ */ export const MAX_FAILED_DELIVERIES_PER_WINDOW = 0;
910
+ /**
911
+ * Inert deliveries tolerated in the window (AGL-1954).
912
+ *
913
+ * Zero — and unlike `processed`, the count this check reports but refuses to
914
+ * gate on because no defensible floor exists before the beta produces a
915
+ * baseline, this one's floor follows from the definition. A delivery is
916
+ * counted inert only when a required event type produced neither a committed
917
+ * effect nor a NAMED deliberate skip (`classifyWebhookDelivery`). No
918
+ * legitimate traffic pattern generates one: a tenant shopper's subscription,
919
+ * a marketplace refund, a `won` dispute nobody claimed all name their reason
920
+ * and classify as `ignored`. So any at all is the statement "Stripe told us
921
+ * about money and nothing here moved" — the same "any at all" rule the
922
+ * rate-limiter degradation markers use (AGL-1679).
923
+ */ export const MAX_INERT_DELIVERIES_PER_WINDOW = 0;
924
+ /**
925
+ * Retried deliveries tolerated in the window (AGL-2039).
926
+ *
927
+ * Zero, and the reasoning is `MAX_FAILED_DELIVERIES_PER_WINDOW`'s: a delivery
928
+ * counted here is one whose FIRST attempt did not get through, which is a
929
+ * failed attempt that happened, not a statistical wobble. The healthy band
930
+ * measured against the live account is 1.0–3.7 seconds against a 120-second
931
+ * bar, so nothing organic sits anywhere near it.
932
+ *
933
+ * Cheap to be wrong in the safe direction, because this cannot latch: the
934
+ * window is trailing and the marker is on the event's own claim, so a single
935
+ * blip reds for at most one window and then clears itself — an event, not a
936
+ * condition (the AGL-1843 rule). It is a parameter rather than a literal so
937
+ * that if beta traffic turns out to produce organic retries, the knob is one
938
+ * argument and not a rewrite.
939
+ */ export const MAX_RETRIED_DELIVERIES_PER_WINDOW = 0;
940
+ export function billingWebhookHealth(facts, ms, windowMinutes = WEBHOOK_FAILURE_WINDOW_MINUTES, threshold = MAX_FAILED_DELIVERIES_PER_WINDOW, inertThreshold = MAX_INERT_DELIVERIES_PER_WINDOW, retriedThreshold = MAX_RETRIED_DELIVERIES_PER_WINDOW) {
941
+ // A null census is degraded, the same rule `signupsHealth` and
942
+ // `rateLimitsHealth` follow: an alarm that cannot see the thing it watches
943
+ // must not report calm. Here it is also the AGL-1906 lesson — `unknown` was
944
+ // read as `healthy` on 2026-08-14 and the endpoint was 400ing every
945
+ // delivery at the time.
946
+ if (facts === null) {
947
+ return {
948
+ ok: false,
949
+ ms,
950
+ code: 'stripe-unavailable',
951
+ endpointStatus: null,
952
+ undelivered: null,
953
+ emitted: null,
954
+ processed: null,
955
+ inert: null,
956
+ retried: null,
957
+ unsubscribedEvents: null,
958
+ connectEndpoint: null,
959
+ unsubscribedConnectEvents: null,
960
+ windowMinutes
961
+ };
962
+ }
963
+ const base = {
964
+ ms,
965
+ endpointStatus: facts.endpointStatus,
966
+ undelivered: facts.undelivered,
967
+ emitted: facts.emitted,
968
+ processed: facts.processed,
969
+ inert: facts.inert,
970
+ retried: facts.retried,
971
+ unsubscribedEvents: facts.unsubscribedEvents,
972
+ connectEndpoint: facts.connectEndpoint,
973
+ unsubscribedConnectEvents: facts.unsubscribedConnectEvents,
974
+ windowMinutes
975
+ };
976
+ // Order matters: a missing or disabled destination explains any delivery
977
+ // count that follows, and is the more actionable statement. An endpoint
978
+ // deleted or switched off in the dashboard is TOTAL silent failure — Stripe
979
+ // stops attempting, so `undelivered` would read zero and a delivery-only
980
+ // rule would call it healthy.
981
+ if (facts.endpointStatus === 'missing') {
982
+ return _extends({}, base, {
983
+ ok: false,
984
+ code: 'endpoint-missing'
985
+ });
986
+ }
987
+ if (facts.endpointStatus === 'disabled') {
988
+ return _extends({}, base, {
989
+ ok: false,
990
+ code: 'endpoint-disabled'
991
+ });
992
+ }
993
+ // A required event the destination does not carry (AGL-1948 / AGL-1798),
994
+ // ABOVE the delivery counts because it explains a silence they cannot see.
995
+ // An unsubscribed event produces no delivery to fail, no request to reject
996
+ // and no inert handler either — Stripe simply stops sending, and every
997
+ // count below reads a perfectly healthy zero for as long as it lasts. It
998
+ // also outranks `deliveries-failing` on actionability: this one names the
999
+ // exact event to re-add, and `npm run setup:stripe` fixes it in a minute.
1000
+ //
1001
+ // `null` — the endpoint did not state its subscriptions — is deliberately
1002
+ // NOT red. It is a question that went unanswered, not an answer, and the
1003
+ // check already has `stripe-unavailable` for a census it could not take.
1004
+ if (facts.unsubscribedEvents && facts.unsubscribedEvents.length > 0) {
1005
+ return _extends({}, base, {
1006
+ ok: false,
1007
+ code: 'events-unsubscribed'
1008
+ });
1009
+ }
1010
+ if (facts.undelivered > threshold) {
1011
+ return _extends({}, base, {
1012
+ ok: false,
1013
+ code: 'deliveries-failing'
1014
+ });
1015
+ }
1016
+ // THE 200-THAT-DID-NOTHING (AGL-1954), and it is LAST on purpose: every
1017
+ // code above explains why a handler would have had nothing to do, so
1018
+ // reporting inertness over one of them would name the symptom instead of
1019
+ // the cause. Reached only when Stripe holds an enabled destination
1020
+ // subscribed to everything we asked for and delivered successfully — at
1021
+ // which point "a delivery moved nothing" has no innocent explanation left.
1022
+ if (facts.inert !== null && facts.inert > inertThreshold) {
1023
+ return _extends({}, base, {
1024
+ ok: false,
1025
+ code: 'handlers-inert'
1026
+ });
1027
+ }
1028
+ /*==========================================
1029
+ * THE ATTEMPTS NOTHING ELSE HERE CAN COUNT (AGL-2039).
1030
+ *
1031
+ * `undelivered` above is `delivery_success=false`, a TERMINAL-state filter
1032
+ * over EVENTS. An event that 400s three times and then succeeds on the
1033
+ * fourth reads back `true`, so it is zero there and zero in `emitted`,
1034
+ * `processed` and `inert` too — the handler DID eventually run. Every
1035
+ * number on this check reports a healthy window while three real delivery
1036
+ * attempts failed. That is not a hypothetical: it is the exact reading
1037
+ * AGL-1906 produced, 0.00% against the Stripe Dashboard's 30%, and the
1038
+ * three attempts the Dashboard was counting were AGL-1551's.
1039
+ *
1040
+ * BELOW `deliveries-failing` because an event failing every attempt
1041
+ * explains an event that needed several, and below `handlers-inert`
1042
+ * because a delivery that landed late and then moved nothing is worse than
1043
+ * one that landed late and worked. ABOVE the Connect codes because this is
1044
+ * this hour's revenue path and those are a slower rot.
1045
+ *
1046
+ * Null is not red: the marker is ours, so an unread Firestore is an
1047
+ * unanswered question, and `stripe-unavailable` already covers a census we
1048
+ * could not take.
1049
+ *=========================================*/ if (facts.retried !== null && facts.retried > retriedThreshold) {
1050
+ return _extends({}, base, {
1051
+ ok: false,
1052
+ code: 'deliveries-retried'
1053
+ });
1054
+ }
1055
+ /*==========================================
1056
+ * THE CONNECT DESTINATION (AGL-1948, closing AGL-2122's blind spot).
1057
+ *
1058
+ * A SECOND destination, and every code above is about the first one. The
1059
+ * platform destination can be enabled, fully subscribed, delivering and
1060
+ * acting — the whole check green — while connected-account events reach
1061
+ * nothing at all, because they are delivered only to a destination created
1062
+ * with `connect: true`. AGL-2122 measured exactly that against the live
1063
+ * account on 2026-08-18: one destination, correct URL, all ten events, and
1064
+ * no Connect destination in existence. `account.updated` was handled in two
1065
+ * plugins and could never be delivered, so `syncConnectAccountStatus` never
1066
+ * ran and every merchant's charge-eligibility flag rotted where it stood.
1067
+ *
1068
+ * LAST on purpose, and for a different reason than `handlers-inert` above:
1069
+ * not because the codes above explain it, but because they do not. It is an
1070
+ * independent destination whose failure is slower — merchant readiness
1071
+ * drifting out of date — while everything above is this hour's revenue. The
1072
+ * facts ride in the body either way, so the staff board shows the Connect
1073
+ * state even when a louder code takes the headline; only the single `code`
1074
+ * is exclusive.
1075
+ *
1076
+ * `missing` IS red. Before AGL-2122 that would have been wrong, because
1077
+ * nothing created the destination and a permanently-red check teaches
1078
+ * people to ignore the board. Now that setup-stripe creates it, its absence
1079
+ * is a real regression with a one-line remedy.
1080
+ *=========================================*/ if (facts.connectEndpoint === 'missing') {
1081
+ return _extends({}, base, {
1082
+ ok: false,
1083
+ code: 'connect-endpoint-missing'
1084
+ });
1085
+ }
1086
+ if (facts.connectEndpoint === 'disabled') {
1087
+ return _extends({}, base, {
1088
+ ok: false,
1089
+ code: 'connect-endpoint-disabled'
1090
+ });
1091
+ }
1092
+ if (facts.unsubscribedConnectEvents && facts.unsubscribedConnectEvents.length > 0) {
1093
+ return _extends({}, base, {
1094
+ ok: false,
1095
+ code: 'connect-events-unsubscribed'
1096
+ });
1097
+ }
1098
+ return _extends({}, base, {
1099
+ ok: true
1100
+ });
1101
+ }
1102
+ export function meteredPricingHealth(facts, ms) {
1103
+ const base = {
1104
+ ms,
1105
+ stripeConfigured: facts.stripeConfigured,
1106
+ monthly: facts.monthly,
1107
+ yearly: facts.yearly
1108
+ };
1109
+ // Unprovisioned: no Stripe, no money, no alarm.
1110
+ if (!facts.stripeConfigured) {
1111
+ return _extends({}, base, {
1112
+ ok: true,
1113
+ code: 'stripe-unconfigured',
1114
+ unbilledInterval: null
1115
+ });
1116
+ }
1117
+ if (facts.monthly && facts.yearly) {
1118
+ return _extends({}, base, {
1119
+ ok: true,
1120
+ unbilledInterval: null
1121
+ });
1122
+ }
1123
+ if (!facts.monthly && !facts.yearly) {
1124
+ return _extends({}, base, {
1125
+ ok: false,
1126
+ code: 'metered-price-missing',
1127
+ unbilledInterval: 'both'
1128
+ });
1129
+ }
1130
+ // Exactly one. The AGL-1931 shape.
1131
+ return _extends({}, base, {
1132
+ ok: false,
1133
+ code: 'metered-price-asymmetric',
1134
+ unbilledInterval: facts.yearly ? 'month' : 'year'
1135
+ });
1136
+ }
1137
+ /*==========================================
1138
+ * SCHEDULED JOBS, WATCHED BY ABSENCE (AGL-1955).
1139
+ *
1140
+ * ## The failure this exists for
1141
+ *
1142
+ * `Cloud Scheduler job run failed (aglyn-main)` is a log-match on
1143
+ * `resource.type="cloud_scheduler_job" AND severity >= ERROR`, and the
1144
+ * `scheduled-crons.yml` workflow goes red on a non-200. Both of those are
1145
+ * triggered BY A RUN. A job that is deleted, paused, or whose `- cron:` line
1146
+ * is edited away produces no attempt, therefore no error entry, therefore no
1147
+ * red workflow, therefore nothing. Quiet reads as healthy — the same shape
1148
+ * AGL-1923 closed for the error beacon, one subsystem over.
1149
+ *
1150
+ * What is downstream of these jobs is not cosmetic: `report-usage` is the
1151
+ * only writer that meters a closed month into Stripe, `run-erasures` is the
1152
+ * GDPR deletion runner, `audit-archive` moves and then DELETES audit rows,
1153
+ * and `plugin-jobs-beat` is the only thing that makes scheduled publishing
1154
+ * and booking-hold expiry happen at all. A silently unscheduled job means
1155
+ * customers are not billed, or data is not reaped, behind a green board.
1156
+ *
1157
+ * ## The two properties that make this work
1158
+ *
1159
+ * **It does not need the job to be alive to fire.** That is the whole bug.
1160
+ * The verdict is computed by the READER — `/api/health/crons`, wound by the
1161
+ * uptime probe and by any staff member opening the Health page — against
1162
+ * marks the jobs left behind. Nothing here is on a schedule that could
1163
+ * itself stop; that would only move the problem one layer out, which is the
1164
+ * AGL-1923 argument for a graded switch over a `conditionAbsent` policy.
1165
+ *
1166
+ * **It cannot false-alarm on a legitimately idle period.** The mark is
1167
+ * stamped by the INVOCATION, not by the work: a `finish-domain-attachments`
1168
+ * run with no pending host still beats, and a quiet week for
1169
+ * `reap-plugin-artifacts` still beats. And the expected time is computed
1170
+ * from the job's own cron expression rather than from a fixed interval, so
1171
+ * `usage-email` — hourly, but only on the 1st and 2nd of the month — is
1172
+ * green for the twenty-nine days it is deliberately not running. A fixed
1173
+ * "expect one every hour" rule would have paged on the 3rd, every month.
1174
+ *
1175
+ * ## Why an output age rule was not enough on its own
1176
+ *
1177
+ * AGL-1843's `exportsHealth` watches the weekly export by the age of what it
1178
+ * PRODUCES, and that is the better rule where an output exists — it catches
1179
+ * "ran and failed" as well as "never ran". Most of these jobs have no such
1180
+ * output. `backfill-scope` is detect-only and deliberately writes nothing.
1181
+ * `usage-alerts` produces a notification only when somebody is over budget.
1182
+ * `reap-plugin-artifacts` deletes nothing in a normal week. For those, the
1183
+ * only honest thing whose age moves on every run is a mark the run itself
1184
+ * leaves — so that is what this reads. `exportsHealth` stays where it is:
1185
+ * the two are complementary, and `firestore-export` is watched by both.
1186
+ *
1187
+ * ## It must be able to go GREEN
1188
+ *
1189
+ * Nothing latches. A job that missed a fire and then runs again stamps a
1190
+ * fresh mark and reads healthy on the next probe — the AGL-1843 rule, whose
1191
+ * clearing event here is named and is "the next invocation of that job".
1192
+ *=========================================*/ /** Where the marks live. One document per job id, written by the job. */ export const CRON_BEAT_COLLECTION = 'platformCronBeats';
1193
+ /**
1194
+ * The document that records when this deployment STARTED watching.
1195
+ *
1196
+ * Without it a job that has never reported cannot be told apart from a job
1197
+ * that has stopped reporting, and every board would come up red on the day
1198
+ * the feature deploys — for `usage-email`, red until the 1st of the next
1199
+ * month. The rule this enables is stated once, in `cronJobsHealth`: a job
1200
+ * with no mark is only silent once a scheduled fire time has passed SINCE
1201
+ * we started watching.
1202
+ *
1203
+ * Not a reserved id: Firestore reserves `__.*__`, and this is a plain slug
1204
+ * that no job id can collide with (job ids never contain the word `watch`,
1205
+ * and the wiring spec asserts the inventory's ids against the workflow).
1206
+ */ export const CRON_BEAT_WATCH_DOC = 'watch-window';
1207
+ /**
1208
+ * Where a degraded verdict is recorded, so a red window survives it.
1209
+ *
1210
+ * The response body names the late job, but only to whoever is holding it,
1211
+ * and the uptime probe reads the status and discards the body. Vercel's
1212
+ * runtime log keeps a `console.error` for about an hour, and the Cloud
1213
+ * Logging drain deliberately does not forward one — `isServerErrorEntry`
1214
+ * drops `level: 'error'`, because that is every fail-soft line the beacons
1215
+ * write and forwarding it is what a self-feeding bill is made of. So the
1216
+ * only sink that outlives the window is the one the endpoint already holds
1217
+ * a handle to.
1218
+ *
1219
+ * Written by the READER, like the verdict itself. One document, replaced on
1220
+ * each degraded probe, holding the failing rows and when the window opened.
1221
+ * Same collision argument as the watch document: a plain slug, and no job id
1222
+ * contains `degraded`.
1223
+ */ export const CRON_BEAT_DEGRADED_DOC = 'last-degraded';
1224
+ /**
1225
+ * Every job's latest mark in one document, `{ beats: { [jobId]: atMs } }`.
1226
+ *
1227
+ * The reader's fast path (AGL-2946). Listing the collection costs one read per
1228
+ * job on every uncached probe — ~23 reads, several hundred times a day, on a
1229
+ * project that pays for reads past the free tier — to answer a question this
1230
+ * one document answers.
1231
+ *
1232
+ * A COPY, never the authority. `writeCronBeat` writes it second, so a job
1233
+ * whose own mark landed can still be missing or stale here, and the reader
1234
+ * confirms any job this document calls late against that job's own mark
1235
+ * before reporting it. Stale costs a read; it never costs a false red.
1236
+ *
1237
+ * Same collision argument as the other two: a plain slug no job id equals.
1238
+ */ export const CRON_BEAT_SUMMARY_DOC = 'summary';
1239
+ /** The reserved documents in `CRON_BEAT_COLLECTION` that are not job marks. */ export const CRON_BEAT_RESERVED_DOCS = new Set([
1240
+ CRON_BEAT_WATCH_DOC,
1241
+ CRON_BEAT_DEGRADED_DOC,
1242
+ CRON_BEAT_SUMMARY_DOC
1243
+ ]);
1244
+ /**
1245
+ * How long a gap may be before the next degraded probe counts as a NEW
1246
+ * window rather than a continuation of the one on file.
1247
+ *
1248
+ * Three probe TTLs. Long enough that an instance answering intermittently
1249
+ * does not split one outage into a dozen, short enough that yesterday's
1250
+ * window is never mistaken for today's. The comparison is against the
1251
+ * failing set as well, so a different set always opens a new window.
1252
+ */ export const CRON_DEGRADED_WINDOW_CONTINUITY_MS = 15 * 60000;
1253
+ /**
1254
+ * THE INVENTORY.
1255
+ *
1256
+ * Seven GitHub Actions schedules (`.github/workflows/scheduled-crons.yml`) —
1257
+ * the weekly jobs, the month-boundary usage-email sweep and the hourly CRM
1258
+ * task reminders, for which an hour of drift is nothing — and the rows
1259
+ * driven by Cloud Scheduler out of
1260
+ * `cloud/functions/src/index.ts`: `pluginJobsBeat` and `consoleAiJobsBeat`
1261
+ * (every minute), the routes the `consoleFastCrons` job carries every
1262
+ * fifteen (AGL-1617), and one `consoleDailyCron` export per daily job.
1263
+ *
1264
+ * `scheduled-crons-wiring.spec.ts` holds BOTH runners against their source —
1265
+ * the workflow for the `github-actions` rows, the functions file for the
1266
+ * `cloud-scheduler` ones — in both directions. An inventory row for a job
1267
+ * nobody schedules and a scheduled job nobody watches are the same bug seen
1268
+ * from opposite ends, and both fail the build.
1269
+ *
1270
+ * `report-usage` appears TWICE and is two jobs, not one. The 02:00 run
1271
+ * rolls up the closed month and is the only run that ever reaches Stripe;
1272
+ * the 07:00 `?month=current` run writes the in-progress figure every usage
1273
+ * budget reads (AGL-2219). Either can stop without the other, and folding
1274
+ * them into one row would hide exactly that.
1275
+ */ /*
1276
+ * ⚠️ GRACE IS A PROPERTY OF THE RUNNER, not of the job.
1277
+ *
1278
+ * The five daily console jobs carried a SIX-HOUR grace because GitHub
1279
+ * Actions dispatches scheduled workflows on a best-effort basis and routinely
1280
+ * drifted by an hour or two. That grace bought tolerance and paid for it in
1281
+ * blindness: on 2026-08-27 GitHub dropped the whole day, and three of these
1282
+ * were thirty hours silent before anything said so.
1283
+ *
1284
+ * They run on Cloud Scheduler now, which has been punctual to the minute
1285
+ * since AGL-1617, and each function's own ceiling is 540 seconds. Ninety
1286
+ * minutes is therefore ten times the longest a healthy run can take and still
1287
+ * catches a dead job inside the working day, instead of most of a day later.
1288
+ */ export const SCHEDULED_JOBS = [
1289
+ {
1290
+ id: 'report-usage',
1291
+ label: 'Metered usage rollup (closed month)',
1292
+ cron: '0 2 * * *',
1293
+ runner: 'cloud-scheduler',
1294
+ target: '/api/billing/report-usage',
1295
+ graceMinutes: 90,
1296
+ drives: 'The only run that meters a closed month into Stripe. If it stops, customers are not billed for what they used.'
1297
+ },
1298
+ {
1299
+ id: 'audit-archive',
1300
+ label: 'Audit archive',
1301
+ cron: '0 3 * * *',
1302
+ runner: 'cloud-scheduler',
1303
+ target: '/api/admin/audit-archive',
1304
+ graceMinutes: 90,
1305
+ drives: 'Moves adminAudit rows past the 90-day window into Storage and deletes them from Firestore. If it stops, the retention promise is not being kept.'
1306
+ },
1307
+ {
1308
+ id: 'run-erasures',
1309
+ label: 'GDPR erasure runner',
1310
+ cron: '0 4 * * *',
1311
+ runner: 'cloud-scheduler',
1312
+ target: '/api/admin/run-erasures',
1313
+ graceMinutes: 90,
1314
+ drives: 'Executes accepted erasure requests. If it stops, personal data a customer asked us to delete is still here, and the clock on that request is still running.'
1315
+ },
1316
+ {
1317
+ id: 'report-usage-current',
1318
+ label: 'Metered usage rollup (current month)',
1319
+ cron: '0 7 * * *',
1320
+ runner: 'cloud-scheduler',
1321
+ target: '/api/billing/report-usage?month=current',
1322
+ graceMinutes: 90,
1323
+ drives: 'Writes the in-progress month figure (AGL-2219). If it stops, every usage budget on the platform is structurally unable to fire and the Billing card stops totalling.'
1324
+ },
1325
+ {
1326
+ id: 'usage-alerts',
1327
+ label: 'Usage budget alerts',
1328
+ cron: '0 8 * * *',
1329
+ runner: 'cloud-scheduler',
1330
+ target: '/api/billing/usage-alerts',
1331
+ graceMinutes: 90,
1332
+ drives: 'Evaluates usage budgets and notifies. If it stops, an org sails past its budget with no warning and finds out on the invoice.'
1333
+ },
1334
+ {
1335
+ id: 'reap-sending-domains',
1336
+ label: 'Orphaned sending-domain reaper',
1337
+ // An hour after `run-erasures`, which is what produces most of its work:
1338
+ // an erasure records what it could not release rather than waiting on a
1339
+ // vendor, and this is the only thing that settles that debt.
1340
+ cron: '0 5 * * *',
1341
+ runner: 'cloud-scheduler',
1342
+ target: '/api/admin/reap-sending-domains',
1343
+ graceMinutes: 90,
1344
+ drives: 'Releases the provider domain object and the zone records of every sending domain whose site or workspace is gone. If it stops, each deleted site keeps one of a bounded number of provider domain slots forever — until the ceiling is reached and a site that asks for a domain of its own is refused one, which leaves it on the shared pool rather than stopping its mail — and leaves a live DKIM key in our zone under a label a future site can claim and inherit a stranger’s signature from.'
1345
+ },
1346
+ {
1347
+ id: 'reap-unverified-orgs',
1348
+ label: 'Unverified signup reaper',
1349
+ // 06:00, after `run-erasures` and the sending-domain sweep and before the
1350
+ // billing pair. It calls `eraseOrg` itself, so it wants the erasure runner
1351
+ // to have already finished with whatever it was holding.
1352
+ cron: '0 6 * * *',
1353
+ runner: 'cloud-scheduler',
1354
+ target: '/api/admin/reap-unverified-orgs?dryRun=0',
1355
+ graceMinutes: 90,
1356
+ drives: 'Erases workspaces whose sole owner never confirmed an email address, releasing the workspace address they took, and promotes the held address of every owner who has since verified. If it stops, a name claimed with a throwaway inbox is held until its reservation expires — and, worse, a real customer who verified keeps a pending address that becomes claimable by anyone on day twenty-one.'
1357
+ },
1358
+ {
1359
+ id: 'ai-insights-digest',
1360
+ label: 'Weekly insights',
1361
+ // 06:00 and 14:00 every day (AGL-2915): Monday's first run makes the
1362
+ // week's digests and every run after it delivers the ones the AI jobs beat
1363
+ // has written. Served by the AI plugin.
1364
+ cron: '0 6,14 * * *',
1365
+ runner: 'cloud-scheduler',
1366
+ target: '/api/admin/ai-insights-digest',
1367
+ graceMinutes: 90,
1368
+ drives: 'Each Monday, makes the weekly insights people asked for, and delivers them once they are written. If it stops, nobody who asked is sent their insights, and a digest parked for credits is never given up.'
1369
+ },
1370
+ {
1371
+ id: 'usage-email',
1372
+ label: 'Monthly usage summaries',
1373
+ // Hourly across the FIRST TWO DAYS only (AGL-2409). The idle-period case
1374
+ // this whole check had to get right: for twenty-nine days a month this
1375
+ // job is correctly doing nothing, and must read green while it does.
1376
+ cron: '0 * 1-2 * *',
1377
+ runner: 'github-actions',
1378
+ target: '/api/billing/usage-email',
1379
+ graceMinutes: 360,
1380
+ drives: "Mails each org last month's usage summary, chunked across 48 hourly windows. If it stops, the month's summaries are simply never sent."
1381
+ },
1382
+ {
1383
+ id: 'crm-task-reminders',
1384
+ label: 'CRM task reminders',
1385
+ // Hourly, all month (AGL-2659). The grace is `usage-email`'s, for the
1386
+ // reason that row gives about this runner: GitHub's dispatch is
1387
+ // best-effort, and a reminder stays owed on the task until a run
1388
+ // handles it, so a dropped hour costs lateness rather than silence.
1389
+ cron: '0 * * * *',
1390
+ runner: 'github-actions',
1391
+ target: '/api/crm/task-reminders',
1392
+ graceMinutes: 360,
1393
+ drives: 'Sends each CRM task’s reminder at its own time — a console notification and an email to the assignee (AGL-2659). If it stops, every reminder a person set in the task drawer is silently late, and nothing but the morning digest says a task was due.'
1394
+ },
1395
+ {
1396
+ id: 'firestore-export',
1397
+ label: 'Weekly Firestore export',
1398
+ cron: '0 5 * * 1',
1399
+ runner: 'github-actions',
1400
+ target: '/api/admin/firestore-export',
1401
+ graceMinutes: 1440,
1402
+ drives: 'Starts the independent GCS export — the restore point that exists because the managed backups kept flipping to NOT_AVAILABLE (AGL-1843). Also watched by output age on /api/health/backups.'
1403
+ },
1404
+ {
1405
+ id: 'reap-plugin-artifacts',
1406
+ label: 'Plugin artifact reaper',
1407
+ cron: '30 5 * * 1',
1408
+ runner: 'github-actions',
1409
+ target: '/api/admin/reap-plugin-artifacts',
1410
+ graceMinutes: 1440,
1411
+ drives: 'Deletes orphaned plugin bundles from the artifacts bucket. If it stops, storage grows without bound and nothing says so.'
1412
+ },
1413
+ {
1414
+ id: 'reverify-plugin-versions',
1415
+ label: 'Plugin verdict re-verification',
1416
+ cron: '0 6 * * 1',
1417
+ runner: 'github-actions',
1418
+ target: '/api/admin/reverify-plugin-versions',
1419
+ graceMinutes: 1440,
1420
+ drives: 'Re-checks stored plugin verdicts against the current verifier (AGL-1086). If it stops, a regression on a live version is never noticed.'
1421
+ },
1422
+ {
1423
+ id: 'reverify-sso-domains',
1424
+ label: 'SSO domain re-verification',
1425
+ // Mondays 07:30 UTC, after the rest of the weekly block. Weekly is the
1426
+ // right cadence because the threat needs a domain to lapse, clear a
1427
+ // registrar grace period and be re-bought — months, not days — and a
1428
+ // daily sweep would multiply DNS traffic by seven to learn nothing sooner.
1429
+ cron: '30 7 * * 1',
1430
+ runner: 'github-actions',
1431
+ target: '/api/admin/reverify-sso-domains',
1432
+ graceMinutes: 1440,
1433
+ drives: 'Re-checks that every live SSO domain still publishes its DNS ownership token (AGL-1210). If it stops, a domain that changed hands keeps routing another company sign-ins and nothing notices. It reports only — it never revokes, because a revoke logs out an entire enterprise.'
1434
+ },
1435
+ {
1436
+ id: 'backfill-scope',
1437
+ label: 'Scope-drift detection',
1438
+ cron: '30 6 * * 1',
1439
+ runner: 'github-actions',
1440
+ target: '/api/admin/backfill-scope',
1441
+ graceMinutes: 1440,
1442
+ drives: 'Reports documents with no visibleTo — invisible to every site-scoped read (AGL-1478). Detect-only by construction, so it has no output whose age could stand in for this mark.'
1443
+ },
1444
+ {
1445
+ id: 'campaigns-process-scheduled',
1446
+ label: 'Scheduled email campaigns',
1447
+ cron: '*/15 * * * *',
1448
+ // MOVED OFF GITHUB ACTIONS (AGL-1617), with the row below it. See the
1449
+ // grace note on `graceMinutes` for why that let the bar go UP.
1450
+ runner: 'cloud-scheduler',
1451
+ target: 'consoleFastCrons → console /api/campaigns/process-scheduled',
1452
+ graceMinutes: 45,
1453
+ drives: 'Claims and sends due campaigns. If it stops, a campaign the composer showed as Scheduled sits there and never sends — the AGL-2134 shape, which is sold on /product/marketing.'
1454
+ },
1455
+ {
1456
+ id: 'lists-materialize',
1457
+ label: 'Dynamic email lists',
1458
+ // Shares the `consoleFastCrons` job with the two rows around it — one
1459
+ // Cloud Scheduler job driving three routes, which is why adding this cost
1460
+ // no scheduler quota.
1461
+ cron: '*/15 * * * *',
1462
+ runner: 'cloud-scheduler',
1463
+ target: 'consoleFastCrons \u2192 console /api/lists/materialize',
1464
+ graceMinutes: 45,
1465
+ drives: 'Re-evaluates every dynamic list against its rule. If it stops, a list keeps whatever membership it last had, and a campaign sent to it reaches an audience that silently stopped tracking the rule the merchant wrote — which looks exactly like a rule that matched nobody new.'
1466
+ },
1467
+ {
1468
+ id: 'finish-domain-attachments',
1469
+ label: 'Custom domain re-check',
1470
+ // Every fifteen minutes rather than twenty since AGL-1617: it shares one
1471
+ // Cloud Scheduler job with the campaign processor, and AGL-2010's argument
1472
+ // was always that a customer is sitting there waiting for their site.
1473
+ cron: '*/15 * * * *',
1474
+ runner: 'cloud-scheduler',
1475
+ target: 'consoleFastCrons → console /api/admin/finish-domain-attachments',
1476
+ graceMinutes: 45,
1477
+ drives: 'Re-checks pending custom domains after the certificate or DNS settles (AGL-2010). If it stops, a correctly-configured domain stays dark until a human presses Re-attach.'
1478
+ },
1479
+ {
1480
+ id: 'provision-sending-domains',
1481
+ label: 'Sending domain provisioning',
1482
+ // The fourth route on the `consoleFastCrons` job, sharing its schedule
1483
+ // with the three rows above. It is a console route rather than a job on
1484
+ // the platform beat because issuing a DKIM key needs a full-access
1485
+ // provider credential the tenant runtime must never hold.
1486
+ cron: '*/15 * * * *',
1487
+ runner: 'cloud-scheduler',
1488
+ target: 'consoleFastCrons → console /api/admin/provision-sending-domains',
1489
+ graceMinutes: 45,
1490
+ drives: 'Turns a site’s sending-domain claim into a DKIM key and the DNS records that carry it. If it stops, every site that has ASKED for a domain of its own waits on a claim with no records to publish. Their mail keeps leaving on the shared pool throughout, which makes this a stall in reputation isolation rather than stopped mail — and it is invisible to the merchant, who sees only a domain that never finishes.'
1491
+ },
1492
+ {
1493
+ id: 'drain-publish-outbox',
1494
+ label: 'Publish outbox drain',
1495
+ // The fifth route on the `consoleFastCrons` job. Fifteen minutes is the
1496
+ // bound on how long a publish whose tab went away can stay invisible,
1497
+ // and it is the schedule this shares rather than one chosen for it —
1498
+ // buying a tighter one would mean a second Cloud Scheduler job against a
1499
+ // free-tier allowance of three.
1500
+ cron: '*/15 * * * *',
1501
+ runner: 'cloud-scheduler',
1502
+ target: 'consoleFastCrons \u2192 console /api/admin/drain-publish-outbox',
1503
+ graceMinutes: 45,
1504
+ drives: 'Fires the cache-drop announce for publishes whose tab closed before it landed (AGL-2575). Publishing is a client Firestore write, so the announce is a fetch from the browser and a closed tab strands it; this is the only thing that finishes one. If it stops, a stranded publish is invisible on the live site for the full hour-long document TTL, and the pending entry that records it is never read by anything.'
1505
+ },
1506
+ {
1507
+ id: 'plugin-console-crons',
1508
+ label: 'Plugin console jobs',
1509
+ // The sixth route on the `consoleFastCrons` job (AGL-2981). One route for
1510
+ // every job a plugin declares on `plugin-console-crons`, so a plugin's
1511
+ // job needs no route of its own in this list; each declared job has its
1512
+ // own row beside this one, added by `/api/health/crons` from the
1513
+ // declarations. This row answers the question under all of them: is the
1514
+ // route being posted at all.
1515
+ cron: '*/15 * * * *',
1516
+ runner: 'cloud-scheduler',
1517
+ target: 'consoleFastCrons → console /api/admin/plugin-crons',
1518
+ graceMinutes: 45,
1519
+ drives: 'Runs every console job a plugin declares, each with its own row on this board. If it stops, every one of those jobs stops with it — a plugin’s scheduled sends and syncs among them — and each of their rows goes silent too.'
1520
+ },
1521
+ {
1522
+ id: 'ai-jobs-beat',
1523
+ label: 'AI jobs beat',
1524
+ // Its own Cloud Scheduler job, every minute (AGL-3026): the console route
1525
+ // runs steps for most of five minutes at a time, and a page waits a beat
1526
+ // between sections. The grace is the other every-minute job's, below.
1527
+ cron: '* * * * *',
1528
+ runner: 'cloud-scheduler',
1529
+ target: 'consoleAiJobsBeat → console /api/admin/ai-jobs-beat',
1530
+ graceMinutes: 30,
1531
+ drives: 'Runs the AI generation steps the doors leave queued: every plan, every page pass, the passes of a site scaffold, and any step an inline door’s budget handed back (AGL-3026). It runs on the console because only the console holds the AI provider key. If it stops, a generation job that plans never gets its plan and a page is never built — the job sits queued, spending nothing, and never finishes.'
1532
+ },
1533
+ {
1534
+ id: 'plugin-jobs-beat',
1535
+ label: 'Plugin job beat',
1536
+ // Cloud Scheduler says `every 1 minutes`; the equivalent five-field
1537
+ // expression is what this check evaluates against.
1538
+ cron: '* * * * *',
1539
+ runner: 'cloud-scheduler',
1540
+ target: 'firebase-schedule-pluginJobsBeat-us-central1 → tenant /api/plugins/run-jobs',
1541
+ graceMinutes: 30,
1542
+ drives: 'The only thing that runs scheduled publishing and booking-hold expiry (AGL-1159). It is also the one job that can fail by pointing at the WRONG deployment (AGL-2176) — the mark is stamped by the tenant runner that answers, so a beat aimed elsewhere reads as silence here.'
1543
+ }
1544
+ ];
1545
+ /**
1546
+ * A cron field, expanded to the set of values it matches.
1547
+ *
1548
+ * Supports a star, `a`, `a-b`, `a,b,c` and a trailing `/n` step on any of
1549
+ * them — every form the inventory and `scheduled-crons.yml` use, and nothing
1550
+ * else. An unparsable field returns null, which the caller treats as "matches
1551
+ * nothing". A schedule that can never fire then produces a null `dueAt` and
1552
+ * a green row rather than a permanent red one, and the wiring spec is what
1553
+ * catches the typo — a monitor that reds on its own parse bug teaches people
1554
+ * to ignore it.
1555
+ */ function expandCronField(field, min, max) {
1556
+ const values = new Set();
1557
+ for (const part of field.split(',')){
1558
+ const [range, stepRaw] = part.split('/');
1559
+ const step = stepRaw === undefined ? 1 : Number.parseInt(stepRaw, 10);
1560
+ if (!Number.isFinite(step) || step <= 0) return null;
1561
+ let from;
1562
+ let to;
1563
+ if (range === '*') {
1564
+ from = min;
1565
+ to = max;
1566
+ } else if (range.includes('-')) {
1567
+ const [a, b] = range.split('-');
1568
+ from = Number.parseInt(a, 10);
1569
+ to = Number.parseInt(b, 10);
1570
+ } else {
1571
+ from = Number.parseInt(range, 10);
1572
+ to = stepRaw === undefined ? from : max;
1573
+ }
1574
+ if (!Number.isFinite(from) || !Number.isFinite(to)) return null;
1575
+ if (from < min || to > max || from > to) return null;
1576
+ for(let value = from; value <= to; value += step)values.add(value);
1577
+ }
1578
+ return values.size ? values : null;
1579
+ }
1580
+ function parseCron(expression) {
1581
+ const fields = expression.trim().split(/\s+/);
1582
+ if (fields.length !== 5) return null;
1583
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
1584
+ const parsed = {
1585
+ minute: expandCronField(minute, 0, 59),
1586
+ hour: expandCronField(hour, 0, 23),
1587
+ dayOfMonth: expandCronField(dayOfMonth, 1, 31),
1588
+ month: expandCronField(month, 1, 12),
1589
+ dayOfWeek: expandCronField(dayOfWeek, 0, 6),
1590
+ domRestricted: dayOfMonth !== '*',
1591
+ dowRestricted: dayOfWeek !== '*'
1592
+ };
1593
+ if (!parsed.minute || !parsed.hour || !parsed.dayOfMonth || !parsed.month || !parsed.dayOfWeek) {
1594
+ return null;
1595
+ }
1596
+ return parsed;
1597
+ }
1598
+ /**
1599
+ * Does this UTC date fall on a day the expression can fire?
1600
+ *
1601
+ * The day-of-month / day-of-week OR is the standard cron rule: when BOTH are
1602
+ * restricted the job fires on either, not on both. No expression in the
1603
+ * inventory restricts both, and the rule is implemented anyway so that one
1604
+ * added later is not silently misjudged.
1605
+ */ function cronDayMatches(parsed, date) {
1606
+ if (!parsed.month.has(date.getUTCMonth() + 1)) return false;
1607
+ const domHit = parsed.dayOfMonth.has(date.getUTCDate());
1608
+ const dowHit = parsed.dayOfWeek.has(date.getUTCDay());
1609
+ if (parsed.domRestricted && parsed.dowRestricted) return domHit || dowHit;
1610
+ if (parsed.domRestricted) return domHit;
1611
+ if (parsed.dowRestricted) return dowHit;
1612
+ return true;
1613
+ }
1614
+ /**
1615
+ * The latest time at or before `atMs` that `expression` fires, UTC.
1616
+ *
1617
+ * Null when the expression is unparsable, or when it does not fire inside
1618
+ * the lookback — 45 days, which comfortably clears the longest gap any
1619
+ * inventory entry has (`usage-email`'s twenty-nine idle days).
1620
+ *
1621
+ * Whole non-matching DAYS are skipped rather than walked minute by minute,
1622
+ * so the worst case is ~45 day checks plus 1,440 minute checks rather than
1623
+ * 64,800 of them.
1624
+ */ export function previousCronFire(expression, atMs, lookbackDays = 45) {
1625
+ const parsed = parseCron(expression);
1626
+ if (!parsed) return null;
1627
+ // Floor to the minute: a fire at 05:00:00 counts as due at 05:00:30.
1628
+ let cursor = Math.floor(atMs / 60000) * 60000;
1629
+ const floor = cursor - lookbackDays * 86400000;
1630
+ while(cursor >= floor){
1631
+ const date = new Date(cursor);
1632
+ if (!cronDayMatches(parsed, date)) {
1633
+ // Jump to 23:59 of the previous UTC day.
1634
+ const startOfDay = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
1635
+ cursor = startOfDay - 60000;
1636
+ continue;
1637
+ }
1638
+ if (parsed.hour.has(date.getUTCHours()) && parsed.minute.has(date.getUTCMinutes())) {
1639
+ return cursor;
1640
+ }
1641
+ cursor -= 60000;
1642
+ }
1643
+ return null;
1644
+ }
1645
+ /**
1646
+ * The scheduled jobs, reduced to one health check per job (AGL-1955).
1647
+ *
1648
+ * `beats` null means the marks could not be read at all — degraded for every
1649
+ * row, the same rule `signupsHealth`, `rateLimitsHealth` and `beaconHealth`
1650
+ * follow. An alarm that cannot see the thing it watches must say so rather
1651
+ * than report calm; here it is the literal condition the issue is about.
1652
+ *
1653
+ * `watchStartedAtMs` is when this deployment first read the marks. A job with
1654
+ * no mark is silent only once a fire time has passed SINCE then — otherwise
1655
+ * every row would be red on the day this deploys, and `usage-email` would
1656
+ * stay red until the 1st.
1657
+ *
1658
+ * Pure on purpose, like its siblings: the route reads, this decides, the
1659
+ * spec exercises every branch without a network.
1660
+ */ export function cronJobsHealth(beats, watchStartedAtMs, ms, now = Date.now(), jobs = SCHEDULED_JOBS) {
1661
+ const byId = new Map((beats != null ? beats : []).map((beat)=>[
1662
+ beat.jobId,
1663
+ beat.atMs
1664
+ ]));
1665
+ const checks = {};
1666
+ for (const job of jobs){
1667
+ var _byId_get;
1668
+ const lastBeatMs = (_byId_get = byId.get(job.id)) != null ? _byId_get : null;
1669
+ const lastBeatAgeMinutes = lastBeatMs === null ? null : Math.round((now - lastBeatMs) / 60000);
1670
+ // The most recent fire time that is already past its grace. Everything
1671
+ // more recent than that is a run we are still waiting for, on time.
1672
+ const dueMs = previousCronFire(job.cron, now - job.graceMinutes * 60000);
1673
+ const base = {
1674
+ ms,
1675
+ schedule: job.cron,
1676
+ runner: job.runner,
1677
+ lastBeatAgeMinutes,
1678
+ dueAt: dueMs === null ? null : new Date(dueMs).toISOString(),
1679
+ graceMinutes: job.graceMinutes
1680
+ };
1681
+ if (beats === null) {
1682
+ checks[job.id] = _extends({}, base, {
1683
+ ok: false,
1684
+ code: 'beats-unavailable'
1685
+ });
1686
+ continue;
1687
+ }
1688
+ if (dueMs === null) {
1689
+ // Nothing was due inside the lookback. Green, and honestly so.
1690
+ checks[job.id] = _extends({}, base, {
1691
+ ok: true
1692
+ });
1693
+ continue;
1694
+ }
1695
+ if (lastBeatMs === null) {
1696
+ // Never reported. Only a defect once a fire time has passed since we
1697
+ // started watching — before that it is a job we have not met yet.
1698
+ const overdue = dueMs > watchStartedAtMs;
1699
+ checks[job.id] = overdue ? _extends({}, base, {
1700
+ ok: false,
1701
+ code: 'job-never-reported'
1702
+ }) : _extends({}, base, {
1703
+ ok: true,
1704
+ code: 'awaiting-first-run'
1705
+ });
1706
+ continue;
1707
+ }
1708
+ checks[job.id] = lastBeatMs < dueMs ? _extends({}, base, {
1709
+ ok: false,
1710
+ code: 'job-silent'
1711
+ }) : _extends({}, base, {
1712
+ ok: true
1713
+ });
1714
+ }
1715
+ return checks;
1716
+ }
1717
+ export async function writeCronBeat(store, jobId, now = Date.now()) {
1718
+ try {
1719
+ const beats = store.collection(CRON_BEAT_COLLECTION);
1720
+ await beats.doc(jobId).set({
1721
+ jobId,
1722
+ atMs: now,
1723
+ at: new Date(now).toISOString()
1724
+ }, {
1725
+ merge: true
1726
+ });
1727
+ try {
1728
+ await beats.doc(CRON_BEAT_SUMMARY_DOC).set({
1729
+ beats: {
1730
+ [jobId]: now
1731
+ }
1732
+ }, {
1733
+ merge: true
1734
+ });
1735
+ } catch (unused) {
1736
+ // The reader confirms a stale summary entry against the mark above.
1737
+ }
1738
+ return true;
1739
+ } catch (unused) {
1740
+ return false;
1741
+ }
1742
+ }
1743
+ /**
1744
+ * The beats a summary document holds, ignoring anything malformed.
1745
+ *
1746
+ * `raw` is the document's `beats` field as Firestore returns it — a plain map,
1747
+ * or undefined when the document does not exist yet.
1748
+ */ export function cronBeatsFromSummary(raw) {
1749
+ if (!raw || typeof raw !== 'object') return [];
1750
+ return Object.entries(raw).map(([jobId, atMs])=>({
1751
+ jobId,
1752
+ atMs: Number(atMs)
1753
+ })).filter((beat)=>!CRON_BEAT_RESERVED_DOCS.has(beat.jobId)).filter((beat)=>Number.isFinite(beat.atMs));
1754
+ }
1755
+ /**
1756
+ * How old the last successful walk may be before this reds.
1757
+ *
1758
+ * Two hours against an hourly schedule — one missed run is tolerated, two are
1759
+ * not. A canary that runs hourly and reds at 61 minutes would page on every
1760
+ * GitHub Actions queue delay, and an alarm that cries wolf on infrastructure
1761
+ * latency is one nobody reads by the time signup actually breaks. Two misses
1762
+ * is a pattern rather than a hiccup.
1763
+ *
1764
+ * This is deliberately NOT generous enough to sleep through AGL-2581: three
1765
+ * days of refused signups would red this inside two hours.
1766
+ */ /**
1767
+ * Workspace-slug prefix the signup canary owns, and the one piece of the
1768
+ * product that has to know the canary exists (AGL-2715).
1769
+ *
1770
+ * ## Why an exclusion is not optional
1771
+ *
1772
+ * The canary creates a real org and then deletes it. `signupDrought` compares
1773
+ * durable ATTEMPT markers against a live COUNT of orgs — and a deleted org
1774
+ * leaves the count while its attempt marker stays. So every canary walk adds
1775
+ * 1 to the denominator and 0 to the numerator, and after three walks the
1776
+ * platform reports `signup-drought` with nothing wrong.
1777
+ *
1778
+ * That is not theoretical. It fired on 2026-09-09 at six attempts against
1779
+ * zero creations — six walks that had each just proved, end to end, that a
1780
+ * stranger could sign up. A monitor that manufactures the outage it watches
1781
+ * for is worse than no monitor, and it is the same alarm fatigue AGL-2714
1782
+ * removed, arriving from inside the house.
1783
+ *
1784
+ * ## Why the slug, and what that costs
1785
+ *
1786
+ * `/api/orgs/create` receives the requested slug before it does anything, so
1787
+ * this needs no secret, no header and no second code path — the canary is
1788
+ * excluded by being what it says it is.
1789
+ *
1790
+ * The cost is that the prefix is caller-supplied: someone naming their
1791
+ * workspace `signup-canary-…` would not be counted in the drought's
1792
+ * denominator. That is bounded to one uncounted attempt per such workspace,
1793
+ * and it cannot hide an outage, because an outage is many attempts by many
1794
+ * people and only the oddly-named ones would go missing.
1795
+ *
1796
+ * The canary declares the same prefix in `tools/e2e/signup-canary.mjs`, which
1797
+ * cannot import this file — it runs outside the workspace. They are held
1798
+ * together by `apps/console/specs/signup-canary-marker-wiring.spec.ts`.
1799
+ */ export const SIGNUP_CANARY_ORG_SLUG_PREFIX = 'signup-canary-';
1800
+ /**
1801
+ * Is this org creation the canary's own?
1802
+ *
1803
+ * Exported as a function rather than leaving callers to compare strings, so
1804
+ * the one place that decides is the one place to read when asking why a
1805
+ * number looks wrong.
1806
+ */ export function isSignupCanaryOrgSlug(slug) {
1807
+ return typeof slug === 'string' && slug.startsWith(SIGNUP_CANARY_ORG_SLUG_PREFIX);
1808
+ }
1809
+ /**
1810
+ * How old the last walk may be before the check goes red.
1811
+ *
1812
+ * Six hours, far wider than the schedule that writes it, and deliberately so.
1813
+ * GitHub Actions serves `schedule` from a best-effort queue that drops most
1814
+ * requests under load: measured 2026-09-10, `uptime-probe.yml` asks for every
1815
+ * fifteen minutes and actually fired at gaps of 1h55m to 4h38m across the
1816
+ * preceding day and a half. A window sized to the cron expression rather than
1817
+ * to that behavior reds on ordinary queue depth.
1818
+ *
1819
+ * Widening it costs no detection speed, because staleness is not how a broken
1820
+ * signup surfaces. A walk that cannot sign up FAILS and still writes its
1821
+ * marker, which reads `signup-walk-failed` on the next poll. Staleness can
1822
+ * only mean the walker never reported — a CI condition, not a product one —
1823
+ * so this window's whole job is to notice a canary that has genuinely stopped,
1824
+ * and it must not fire before then: this check grades the monitor that must
1825
+ * never be muted.
1826
+ */ export const SIGNUP_CANARY_STALE_AFTER_MS = 6 * 60 * 60 * 1000;
1827
+ /**
1828
+ * Grade the last recorded signup walk.
1829
+ *
1830
+ * Pure, like every sibling: the route reads the marker, this decides. Ordered
1831
+ * by which mistake costs most — a missing marker is treated as failure, never
1832
+ * as calm, because "no evidence" and "evidence of success" are the two
1833
+ * readings this whole issue exists to stop anyone from confusing.
1834
+ */ export function signupCanaryHealth(marker, ms, nowMs = Date.now(), staleAfterMs = SIGNUP_CANARY_STALE_AFTER_MS) {
1835
+ const base = {
1836
+ ms,
1837
+ ageMs: null,
1838
+ failedStep: null,
1839
+ walkMs: null,
1840
+ reapedCleanly: null,
1841
+ staleAfterMs
1842
+ };
1843
+ // No marker at all, or a store that would not answer. The canary has never
1844
+ // run, or cannot be read — either way nothing here has demonstrated that a
1845
+ // stranger can sign up, and this check exists to say so out loud.
1846
+ if (!marker || typeof marker.walkedAtMs !== 'number') {
1847
+ return _extends({}, base, {
1848
+ ok: false,
1849
+ code: 'canary-unavailable'
1850
+ });
1851
+ }
1852
+ const ageMs = Math.max(0, nowMs - marker.walkedAtMs);
1853
+ const walkMs = typeof marker.elapsedMs === 'number' ? marker.elapsedMs : null;
1854
+ const reapedCleanly = typeof marker.reapedCleanly === 'boolean' ? marker.reapedCleanly : null;
1855
+ const failedStep = typeof marker.failedStep === 'string' ? marker.failedStep : null;
1856
+ const carried = _extends({}, base, {
1857
+ ageMs,
1858
+ walkMs,
1859
+ reapedCleanly,
1860
+ failedStep
1861
+ });
1862
+ // A stale PASS is not a pass. A walk that succeeded long enough ago says
1863
+ // nothing about the door now, and the whole point of a canary is currency.
1864
+ // Checked before the verdict so a stale failure reports as stale rather
1865
+ // than as a failure that may since have healed.
1866
+ if (ageMs > staleAfterMs) {
1867
+ return _extends({}, carried, {
1868
+ ok: false,
1869
+ code: 'canary-stale'
1870
+ });
1871
+ }
1872
+ if (marker.ok !== true) {
1873
+ return _extends({}, carried, {
1874
+ ok: false,
1875
+ code: 'signup-walk-failed'
1876
+ });
1877
+ }
1878
+ // Walked fine, did not clean up. Red anyway — see the docblock.
1879
+ if (reapedCleanly === false) {
1880
+ return _extends({}, carried, {
1881
+ ok: false,
1882
+ code: 'canary-left-residue'
1883
+ });
1884
+ }
1885
+ return _extends({}, carried, {
1886
+ ok: true
1887
+ });
1888
+ }
1889
+ /**
1890
+ * How long metered page views may sit still before the edge is presumed shut.
1891
+ *
1892
+ * Twelve hours, and the width is deliberate rather than lazy.
1893
+ *
1894
+ * The quietest full day in the sampled week carried 57 views — about 2.4 an
1895
+ * hour on average, which would make six hours of zero look damning. But the
1896
+ * average is the wrong statistic here: measured 2026-09-10, the day's 62 views
1897
+ * came from ONE host of thirteen. Traffic that concentrated arrives in bursts,
1898
+ * and a burst distribution produces long empty stretches with nothing wrong.
1899
+ * A window sized to the average would page on one of them, which is how the
1900
+ * monitor that must never be ignored gets ignored.
1901
+ *
1902
+ * Twelve hours of exactly zero, on a platform serving 57 to 563 views a day,
1903
+ * is not a quiet stretch. It is slower to fire than anyone would like, and it
1904
+ * is the only thing that catches a fully blocked edge at all — nothing else
1905
+ * would ever fire, because every other check rides the bypass.
1906
+ *
1907
+ * ⚑ Tighten it once the real quiet-period distribution is known: the sampler
1908
+ * stamps `advancedAtMs` on every run, so a week of markers says what the true
1909
+ * maximum gap is. Do not tighten it from the daily average.
1910
+ */ export const EDGE_ADMISSION_QUIET_AFTER_MS = 12 * 60 * 60 * 1000;
1911
+ /**
1912
+ * How old the sampler's own reading may be before it stops being evidence.
1913
+ *
1914
+ * Six hours, sized like `SIGNUP_CANARY_STALE_AFTER_MS` to a scheduler that
1915
+ * drops most of what it is asked for (AGL-2723) rather than to the cron
1916
+ * expression. Shorter than the quiet window on purpose: a stalled sampler
1917
+ * should be named as one BEFORE its frozen reading has had time to look like
1918
+ * an edge outage.
1919
+ */ export const EDGE_ADMISSION_SAMPLE_STALE_AFTER_MS = 6 * 60 * 60 * 1000;
1920
+ /**
1921
+ * Grade the last recorded metered-traffic sample.
1922
+ *
1923
+ * Pure, like every sibling. The sampler decides when the total last MOVED and
1924
+ * stamps it; this only asks how long ago that was. Keeping the movement
1925
+ * detection in the writer is what lets the door stay a read: a day rollover
1926
+ * resets the counter to zero, and a grader looking at one sample could not
1927
+ * tell that from traffic stopping.
1928
+ */ export function edgeAdmissionHealth(marker, ms, nowMs = Date.now(), quietAfterMs = EDGE_ADMISSION_QUIET_AFTER_MS, sampleStaleAfterMs = EDGE_ADMISSION_SAMPLE_STALE_AFTER_MS) {
1929
+ const base = {
1930
+ ms,
1931
+ quietMs: null,
1932
+ day: null,
1933
+ total: null,
1934
+ quietAfterMs,
1935
+ sampleAgeMs: null
1936
+ };
1937
+ // No marker, or one that cannot be read. Absence of proof reds here for the
1938
+ // same reason it does on every sibling: "nobody measured" and "traffic is
1939
+ // arriving" are the two readings this exists to keep apart.
1940
+ if (!marker || typeof marker.advancedAtMs !== 'number') {
1941
+ return _extends({}, base, {
1942
+ ok: false,
1943
+ code: 'edge-admission-unavailable'
1944
+ });
1945
+ }
1946
+ const sampleAgeMs = typeof marker.sampledAtMs === 'number' ? Math.max(0, nowMs - marker.sampledAtMs) : null;
1947
+ const carried = _extends({}, base, {
1948
+ quietMs: Math.max(0, nowMs - marker.advancedAtMs),
1949
+ day: typeof marker.day === 'string' ? marker.day : null,
1950
+ total: typeof marker.total === 'number' ? marker.total : null,
1951
+ sampleAgeMs
1952
+ });
1953
+ // ⛔ CHECKED FIRST, AND THE ORDER IS THE POINT.
1954
+ //
1955
+ // `advancedAtMs` only moves when the sampler runs, so a sampler that has
1956
+ // stopped looks exactly like traffic that has stopped — and the responder
1957
+ // would go looking at the firewall while the real fault was a job that
1958
+ // never fired. That is the confusion `canary-stale` was built to avoid on
1959
+ // the check next door, and it costs one comparison to avoid here too.
1960
+ if (sampleAgeMs === null || sampleAgeMs > sampleStaleAfterMs) {
1961
+ return _extends({}, carried, {
1962
+ ok: false,
1963
+ code: 'edge-sample-stale'
1964
+ });
1965
+ }
1966
+ if (carried.quietMs !== null && carried.quietMs > quietAfterMs) {
1967
+ return _extends({}, carried, {
1968
+ ok: false,
1969
+ code: 'edge-admits-nobody'
1970
+ });
1971
+ }
1972
+ return _extends({}, carried, {
1973
+ ok: true
1974
+ });
1975
+ }
1976
+ /** The service signup depends on. See the docblock for why it is the only one. */ export const APP_CHECK_GRADED_SERVICE = 'identitytoolkit.googleapis.com';
1977
+ /**
1978
+ * Window the attestation rate is taken over.
1979
+ *
1980
+ * Six hours, not one. This metric is aggregated by Google on its own schedule
1981
+ * and arrives late, so a one-hour window reads empty for reasons that have
1982
+ * nothing to do with attestation. Six hours is long enough to always carry
1983
+ * traffic on this platform and short enough that a real collapse is caught the
1984
+ * same working day.
1985
+ */ export const APP_CHECK_WINDOW_MINUTES = 6 * 60;
1986
+ /**
1987
+ * Requests below which the rate is not graded.
1988
+ *
1989
+ * A ratio over three requests is noise wearing a percentage. Twenty is a
1990
+ * fraction of the ~940 a day Identity Platform sees, so a healthy platform
1991
+ * always clears it, and a platform quiet enough not to is a platform where
1992
+ * this check has nothing to say.
1993
+ */ export const APP_CHECK_MIN_REQUESTS = 20;
1994
+ /**
1995
+ * The floor the allow rate must clear.
1996
+ *
1997
+ * 0.90, against a measured 0.997. The gap is deliberate: a handful of genuine
1998
+ * denials is ordinary — a stale tab, a bot, a browser that failed its
1999
+ * reCAPTCHA — and a floor set near the observed rate would page on those.
2000
+ *
2001
+ * What this is built to catch is not a drift of a few percent; it is the
2002
+ * documented failure mode of `recaptcha-allowlist.ts`, where an origin that is
2003
+ * attached and routed but not allowlisted renders a console that can never
2004
+ * sign anyone in. That reads as ~0%, not 85%.
2005
+ */ export const APP_CHECK_MIN_ALLOW_RATE = 0.9;
2006
+ /**
2007
+ * Grade attestation for real visitors.
2008
+ *
2009
+ * Pure, like every sibling: something else reads Cloud Monitoring, this
2010
+ * decides. Null samples are degraded rather than calm — an unreadable metric
2011
+ * is not evidence that attestation works, and this check exists precisely
2012
+ * because "we did not look" was being reported as "it is fine".
2013
+ */ export function appCheckAttestationHealth(samples, ms, service = APP_CHECK_GRADED_SERVICE, minimumRequests = APP_CHECK_MIN_REQUESTS, minimumAllowRate = APP_CHECK_MIN_ALLOW_RATE, windowMinutes = APP_CHECK_WINDOW_MINUTES) {
2014
+ const base = {
2015
+ ms,
2016
+ allowRate: null,
2017
+ allowed: 0,
2018
+ denied: 0,
2019
+ minimumRequests,
2020
+ minimumAllowRate,
2021
+ service,
2022
+ deniedBy: {},
2023
+ windowMinutes
2024
+ };
2025
+ if (samples === null) {
2026
+ return _extends({}, base, {
2027
+ ok: false,
2028
+ code: 'attestation-unavailable'
2029
+ });
2030
+ }
2031
+ let allowed = 0;
2032
+ let denied = 0;
2033
+ const deniedBy = {};
2034
+ for (const sample of samples){
2035
+ if (sample.service !== service) continue;
2036
+ // `Number(...) || 0` for the reason every sibling gives: a corrupt count
2037
+ // must not turn the total into NaN, which compares false against every
2038
+ // threshold and would report calm forever.
2039
+ const count = Number(sample.count) || 0;
2040
+ if (sample.result === 'ALLOW') allowed += count;
2041
+ else if (sample.result === 'DENY') {
2042
+ var _deniedBy_why;
2043
+ denied += count;
2044
+ const why = sample.security || 'UNKNOWN';
2045
+ deniedBy[why] = ((_deniedBy_why = deniedBy[why]) != null ? _deniedBy_why : 0) + count;
2046
+ }
2047
+ }
2048
+ const total = allowed + denied;
2049
+ const carried = _extends({}, base, {
2050
+ allowed,
2051
+ denied,
2052
+ deniedBy
2053
+ });
2054
+ // Too little traffic to say anything. Reported, not hidden: an on-call
2055
+ // person reading a green body deserves to know this one abstained.
2056
+ if (total < minimumRequests) {
2057
+ return _extends({}, carried, {
2058
+ ok: true,
2059
+ code: 'not-enough-traffic',
2060
+ allowRate: null
2061
+ });
2062
+ }
2063
+ const allowRate = allowed / total;
2064
+ return _extends({}, carried, {
2065
+ allowRate,
2066
+ ok: allowRate >= minimumAllowRate
2067
+ }, allowRate >= minimumAllowRate ? {} : {
2068
+ code: 'attestation-failing'
2069
+ });
2070
+ }
2071
+
2072
+ //# sourceMappingURL=health-report.js.map