@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.
- package/LICENSE +201 -0
- package/README.md +11 -0
- package/package.json +66 -0
- package/src/index.d.ts +25 -0
- package/src/index.js +26 -0
- package/src/index.js.map +1 -0
- package/src/lib/aglyn.d.ts +31 -0
- package/src/lib/aglyn.js +114 -0
- package/src/lib/aglyn.js.map +1 -0
- package/src/lib/app-utils/abuse-report.d.ts +193 -0
- package/src/lib/app-utils/abuse-report.js +321 -0
- package/src/lib/app-utils/abuse-report.js.map +1 -0
- package/src/lib/app-utils/accept-negotiation.d.ts +129 -0
- package/src/lib/app-utils/accept-negotiation.js +266 -0
- package/src/lib/app-utils/accept-negotiation.js.map +1 -0
- package/src/lib/app-utils/account-emails.d.ts +263 -0
- package/src/lib/app-utils/account-emails.js +264 -0
- package/src/lib/app-utils/account-emails.js.map +1 -0
- package/src/lib/app-utils/actions.d.ts +717 -0
- package/src/lib/app-utils/actions.js +1031 -0
- package/src/lib/app-utils/actions.js.map +1 -0
- package/src/lib/app-utils/activity-presenter.d.ts +152 -0
- package/src/lib/app-utils/activity-presenter.js +326 -0
- package/src/lib/app-utils/activity-presenter.js.map +1 -0
- package/src/lib/app-utils/advertising-tag-mounts.d.ts +133 -0
- package/src/lib/app-utils/advertising-tag-mounts.js +118 -0
- package/src/lib/app-utils/advertising-tag-mounts.js.map +1 -0
- package/src/lib/app-utils/advertising-tags.d.ts +307 -0
- package/src/lib/app-utils/advertising-tags.js +525 -0
- package/src/lib/app-utils/advertising-tags.js.map +1 -0
- package/src/lib/app-utils/agent-api-catalog.d.ts +94 -0
- package/src/lib/app-utils/agent-api-catalog.js +106 -0
- package/src/lib/app-utils/agent-api-catalog.js.map +1 -0
- package/src/lib/app-utils/agent-openapi.d.ts +103 -0
- package/src/lib/app-utils/agent-openapi.js +1128 -0
- package/src/lib/app-utils/agent-openapi.js.map +1 -0
- package/src/lib/app-utils/analytics-beacon.d.ts +154 -0
- package/src/lib/app-utils/analytics-beacon.js +152 -0
- package/src/lib/app-utils/analytics-beacon.js.map +1 -0
- package/src/lib/app-utils/analytics-environment.d.ts +133 -0
- package/src/lib/app-utils/analytics-environment.js +152 -0
- package/src/lib/app-utils/analytics-environment.js.map +1 -0
- package/src/lib/app-utils/analytics-events.d.ts +744 -0
- package/src/lib/app-utils/analytics-events.js +650 -0
- package/src/lib/app-utils/analytics-events.js.map +1 -0
- package/src/lib/app-utils/analytics-link-clicks.d.ts +102 -0
- package/src/lib/app-utils/analytics-link-clicks.js +267 -0
- package/src/lib/app-utils/analytics-link-clicks.js.map +1 -0
- package/src/lib/app-utils/analytics-path-key.d.ts +46 -0
- package/src/lib/app-utils/analytics-path-key.js +46 -0
- package/src/lib/app-utils/analytics-path-key.js.map +1 -0
- package/src/lib/app-utils/api-adapter.d.ts +47 -0
- package/src/lib/app-utils/api-adapter.js +367 -0
- package/src/lib/app-utils/api-adapter.js.map +1 -0
- package/src/lib/app-utils/api-idempotency.d.ts +169 -0
- package/src/lib/app-utils/api-idempotency.js +213 -0
- package/src/lib/app-utils/api-idempotency.js.map +1 -0
- package/src/lib/app-utils/api-plugins.d.ts +210 -0
- package/src/lib/app-utils/api-plugins.js +221 -0
- package/src/lib/app-utils/api-plugins.js.map +1 -0
- package/src/lib/app-utils/assist-credits.d.ts +482 -0
- package/src/lib/app-utils/assist-credits.js +509 -0
- package/src/lib/app-utils/assist-credits.js.map +1 -0
- package/src/lib/app-utils/attribution-attribute.d.ts +34 -0
- package/src/lib/app-utils/attribution-attribute.js +34 -0
- package/src/lib/app-utils/attribution-attribute.js.map +1 -0
- package/src/lib/app-utils/attribution-guard.d.ts +130 -0
- package/src/lib/app-utils/attribution-guard.js +410 -0
- package/src/lib/app-utils/attribution-guard.js.map +1 -0
- package/src/lib/app-utils/author-css.d.ts +123 -0
- package/src/lib/app-utils/author-css.js +275 -0
- package/src/lib/app-utils/author-css.js.map +1 -0
- package/src/lib/app-utils/author-html-markdown.d.ts +31 -0
- package/src/lib/app-utils/author-html-markdown.js +497 -0
- package/src/lib/app-utils/author-html-markdown.js.map +1 -0
- package/src/lib/app-utils/author-html.d.ts +135 -0
- package/src/lib/app-utils/author-html.js +1037 -0
- package/src/lib/app-utils/author-html.js.map +1 -0
- package/src/lib/app-utils/automation-placeholders.d.ts +72 -0
- package/src/lib/app-utils/automation-placeholders.js +207 -0
- package/src/lib/app-utils/automation-placeholders.js.map +1 -0
- package/src/lib/app-utils/bandwidth-cap.d.ts +183 -0
- package/src/lib/app-utils/bandwidth-cap.js +193 -0
- package/src/lib/app-utils/bandwidth-cap.js.map +1 -0
- package/src/lib/app-utils/binding-token-catalog.d.ts +82 -0
- package/src/lib/app-utils/binding-token-catalog.js +293 -0
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -0
- package/src/lib/app-utils/binding-tokens.d.ts +157 -0
- package/src/lib/app-utils/binding-tokens.js +251 -0
- package/src/lib/app-utils/binding-tokens.js.map +1 -0
- package/src/lib/app-utils/breadcrumb-json-ld.d.ts +57 -0
- package/src/lib/app-utils/breadcrumb-json-ld.js +67 -0
- package/src/lib/app-utils/breadcrumb-json-ld.js.map +1 -0
- package/src/lib/app-utils/campaign-attribution.d.ts +117 -0
- package/src/lib/app-utils/campaign-attribution.js +162 -0
- package/src/lib/app-utils/campaign-attribution.js.map +1 -0
- package/src/lib/app-utils/campaign-forwarding.d.ts +220 -0
- package/src/lib/app-utils/campaign-forwarding.js +332 -0
- package/src/lib/app-utils/campaign-forwarding.js.map +1 -0
- package/src/lib/app-utils/campaign-membership.d.ts +109 -0
- package/src/lib/app-utils/campaign-membership.js +176 -0
- package/src/lib/app-utils/campaign-membership.js.map +1 -0
- package/src/lib/app-utils/campaign-touch.d.ts +206 -0
- package/src/lib/app-utils/campaign-touch.js +326 -0
- package/src/lib/app-utils/campaign-touch.js.map +1 -0
- package/src/lib/app-utils/child-contract-compose.d.ts +28 -0
- package/src/lib/app-utils/child-contract-compose.js +140 -0
- package/src/lib/app-utils/child-contract-compose.js.map +1 -0
- package/src/lib/app-utils/child-contract.d.ts +129 -0
- package/src/lib/app-utils/child-contract.js +153 -0
- package/src/lib/app-utils/child-contract.js.map +1 -0
- package/src/lib/app-utils/collection-delete.d.ts +113 -0
- package/src/lib/app-utils/collection-delete.js +120 -0
- package/src/lib/app-utils/collection-delete.js.map +1 -0
- package/src/lib/app-utils/collection-entries.d.ts +833 -0
- package/src/lib/app-utils/collection-entries.js +1285 -0
- package/src/lib/app-utils/collection-entries.js.map +1 -0
- package/src/lib/app-utils/collection-entry-date.d.ts +129 -0
- package/src/lib/app-utils/collection-entry-date.js +183 -0
- package/src/lib/app-utils/collection-entry-date.js.map +1 -0
- package/src/lib/app-utils/collection-kind.d.ts +55 -0
- package/src/lib/app-utils/collection-kind.js +69 -0
- package/src/lib/app-utils/collection-kind.js.map +1 -0
- package/src/lib/app-utils/collection-slug.d.ts +79 -0
- package/src/lib/app-utils/collection-slug.js +70 -0
- package/src/lib/app-utils/collection-slug.js.map +1 -0
- package/src/lib/app-utils/compose-layout-nodes.d.ts +147 -0
- package/src/lib/app-utils/compose-layout-nodes.js +261 -0
- package/src/lib/app-utils/compose-layout-nodes.js.map +1 -0
- package/src/lib/app-utils/compose-layout-props.d.ts +71 -0
- package/src/lib/app-utils/compose-layout-props.js +61 -0
- package/src/lib/app-utils/compose-layout-props.js.map +1 -0
- package/src/lib/app-utils/compose-reusable-components.d.ts +504 -0
- package/src/lib/app-utils/compose-reusable-components.js +1366 -0
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -0
- package/src/lib/app-utils/compress.d.ts +35 -0
- package/src/lib/app-utils/compress.js +38 -0
- package/src/lib/app-utils/compress.js.map +1 -0
- package/src/lib/app-utils/consent-banner-ui.d.ts +226 -0
- package/src/lib/app-utils/consent-banner-ui.js +583 -0
- package/src/lib/app-utils/consent-banner-ui.js.map +1 -0
- package/src/lib/app-utils/consent-groups.d.ts +189 -0
- package/src/lib/app-utils/consent-groups.js +219 -0
- package/src/lib/app-utils/consent-groups.js.map +1 -0
- package/src/lib/app-utils/consent-preferences-dialog.d.ts +58 -0
- package/src/lib/app-utils/consent-preferences-dialog.js +106 -0
- package/src/lib/app-utils/consent-preferences-dialog.js.map +1 -0
- package/src/lib/app-utils/console-record-links.d.ts +163 -0
- package/src/lib/app-utils/console-record-links.js +178 -0
- package/src/lib/app-utils/console-record-links.js.map +1 -0
- package/src/lib/app-utils/console-routes.d.ts +658 -0
- package/src/lib/app-utils/console-routes.js +489 -0
- package/src/lib/app-utils/console-routes.js.map +1 -0
- package/src/lib/app-utils/console-widget-slot-context.d.ts +46 -0
- package/src/lib/app-utils/console-widget-slot-context.js +23 -0
- package/src/lib/app-utils/console-widget-slot-context.js.map +1 -0
- package/src/lib/app-utils/contact-custom-fields.d.ts +191 -0
- package/src/lib/app-utils/contact-custom-fields.js +349 -0
- package/src/lib/app-utils/contact-custom-fields.js.map +1 -0
- package/src/lib/app-utils/contact-holder.d.ts +52 -0
- package/src/lib/app-utils/contact-holder.js +98 -0
- package/src/lib/app-utils/contact-holder.js.map +1 -0
- package/src/lib/app-utils/contact-merge.d.ts +136 -0
- package/src/lib/app-utils/contact-merge.js +379 -0
- package/src/lib/app-utils/contact-merge.js.map +1 -0
- package/src/lib/app-utils/contacts.d.ts +554 -0
- package/src/lib/app-utils/contacts.js +401 -0
- package/src/lib/app-utils/contacts.js.map +1 -0
- package/src/lib/app-utils/content-author-profile.d.ts +68 -0
- package/src/lib/app-utils/content-author-profile.js +118 -0
- package/src/lib/app-utils/content-author-profile.js.map +1 -0
- package/src/lib/app-utils/content-authors.d.ts +662 -0
- package/src/lib/app-utils/content-authors.js +771 -0
- package/src/lib/app-utils/content-authors.js.map +1 -0
- package/src/lib/app-utils/content-schema-type.d.ts +81 -0
- package/src/lib/app-utils/content-schema-type.js +87 -0
- package/src/lib/app-utils/content-schema-type.js.map +1 -0
- package/src/lib/app-utils/contexts.d.ts +35 -0
- package/src/lib/app-utils/contexts.js +35 -0
- package/src/lib/app-utils/contexts.js.map +1 -0
- package/src/lib/app-utils/create-resource-uid.d.ts +18 -0
- package/src/lib/app-utils/create-resource-uid.js +23 -0
- package/src/lib/app-utils/create-resource-uid.js.map +1 -0
- package/src/lib/app-utils/crm-booking.d.ts +75 -0
- package/src/lib/app-utils/crm-booking.js +133 -0
- package/src/lib/app-utils/crm-booking.js.map +1 -0
- package/src/lib/app-utils/crm-csv.d.ts +232 -0
- package/src/lib/app-utils/crm-csv.js +432 -0
- package/src/lib/app-utils/crm-csv.js.map +1 -0
- package/src/lib/app-utils/crm-digest.d.ts +179 -0
- package/src/lib/app-utils/crm-digest.js +271 -0
- package/src/lib/app-utils/crm-digest.js.map +1 -0
- package/src/lib/app-utils/crm-email-templates.d.ts +172 -0
- package/src/lib/app-utils/crm-email-templates.js +382 -0
- package/src/lib/app-utils/crm-email-templates.js.map +1 -0
- package/src/lib/app-utils/crm-inbound.d.ts +245 -0
- package/src/lib/app-utils/crm-inbound.js +407 -0
- package/src/lib/app-utils/crm-inbound.js.map +1 -0
- package/src/lib/app-utils/crm-kinds.d.ts +73 -0
- package/src/lib/app-utils/crm-kinds.js +92 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -0
- package/src/lib/app-utils/crm-next-activity.d.ts +107 -0
- package/src/lib/app-utils/crm-next-activity.js +140 -0
- package/src/lib/app-utils/crm-next-activity.js.map +1 -0
- package/src/lib/app-utils/crm-reports.d.ts +479 -0
- package/src/lib/app-utils/crm-reports.js +680 -0
- package/src/lib/app-utils/crm-reports.js.map +1 -0
- package/src/lib/app-utils/crm-task-reminders.d.ts +129 -0
- package/src/lib/app-utils/crm-task-reminders.js +139 -0
- package/src/lib/app-utils/crm-task-reminders.js.map +1 -0
- package/src/lib/app-utils/crm.d.ts +1546 -0
- package/src/lib/app-utils/crm.js +1814 -0
- package/src/lib/app-utils/crm.js.map +1 -0
- package/src/lib/app-utils/csp-report.d.ts +84 -0
- package/src/lib/app-utils/csp-report.js +177 -0
- package/src/lib/app-utils/csp-report.js.map +1 -0
- package/src/lib/app-utils/csv-import.d.ts +192 -0
- package/src/lib/app-utils/csv-import.js +279 -0
- package/src/lib/app-utils/csv-import.js.map +1 -0
- package/src/lib/app-utils/dataset-csv.d.ts +109 -0
- package/src/lib/app-utils/dataset-csv.js +235 -0
- package/src/lib/app-utils/dataset-csv.js.map +1 -0
- package/src/lib/app-utils/dataset-models.d.ts +194 -0
- package/src/lib/app-utils/dataset-models.js +409 -0
- package/src/lib/app-utils/dataset-models.js.map +1 -0
- package/src/lib/app-utils/dataset-query.d.ts +57 -0
- package/src/lib/app-utils/dataset-query.js +126 -0
- package/src/lib/app-utils/dataset-query.js.map +1 -0
- package/src/lib/app-utils/datasets.d.ts +136 -0
- package/src/lib/app-utils/datasets.js +177 -0
- package/src/lib/app-utils/datasets.js.map +1 -0
- package/src/lib/app-utils/decompress.d.ts +36 -0
- package/src/lib/app-utils/decompress.js +32 -0
- package/src/lib/app-utils/decompress.js.map +1 -0
- package/src/lib/app-utils/dedicated-sending-domain.d.ts +47 -0
- package/src/lib/app-utils/dedicated-sending-domain.js +114 -0
- package/src/lib/app-utils/dedicated-sending-domain.js.map +1 -0
- package/src/lib/app-utils/deferred-image.d.ts +86 -0
- package/src/lib/app-utils/deferred-image.js +83 -0
- package/src/lib/app-utils/deferred-image.js.map +1 -0
- package/src/lib/app-utils/definition-canvas-tree.d.ts +61 -0
- package/src/lib/app-utils/definition-canvas-tree.js +126 -0
- package/src/lib/app-utils/definition-canvas-tree.js.map +1 -0
- package/src/lib/app-utils/deployment-shape.d.ts +167 -0
- package/src/lib/app-utils/deployment-shape.js +238 -0
- package/src/lib/app-utils/deployment-shape.js.map +1 -0
- package/src/lib/app-utils/detect-template-placeholders.d.ts +30 -0
- package/src/lib/app-utils/detect-template-placeholders.js +53 -0
- package/src/lib/app-utils/detect-template-placeholders.js.map +1 -0
- package/src/lib/app-utils/dmca-counter-notice.d.ts +185 -0
- package/src/lib/app-utils/dmca-counter-notice.js +277 -0
- package/src/lib/app-utils/dmca-counter-notice.js.map +1 -0
- package/src/lib/app-utils/docs-deflection.d.ts +156 -0
- package/src/lib/app-utils/docs-deflection.js +547 -0
- package/src/lib/app-utils/docs-deflection.js.map +1 -0
- package/src/lib/app-utils/docs-help.d.ts +57 -0
- package/src/lib/app-utils/docs-help.generated.d.ts +303 -0
- package/src/lib/app-utils/docs-help.generated.js +867 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -0
- package/src/lib/app-utils/docs-help.js +46 -0
- package/src/lib/app-utils/docs-help.js.map +1 -0
- package/src/lib/app-utils/docs-index.generated.d.ts +29 -0
- package/src/lib/app-utils/docs-index.generated.js +8388 -0
- package/src/lib/app-utils/docs-index.generated.js.map +1 -0
- package/src/lib/app-utils/docs-retrieval.d.ts +67 -0
- package/src/lib/app-utils/docs-retrieval.js +188 -0
- package/src/lib/app-utils/docs-retrieval.js.map +1 -0
- package/src/lib/app-utils/document-landmark.d.ts +50 -0
- package/src/lib/app-utils/document-landmark.js +100 -0
- package/src/lib/app-utils/document-landmark.js.map +1 -0
- package/src/lib/app-utils/duplicate-resource.d.ts +101 -0
- package/src/lib/app-utils/duplicate-resource.js +206 -0
- package/src/lib/app-utils/duplicate-resource.js.map +1 -0
- package/src/lib/app-utils/dynamic-list-rule.d.ts +471 -0
- package/src/lib/app-utils/dynamic-list-rule.js +914 -0
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -0
- package/src/lib/app-utils/element-animation.d.ts +200 -0
- package/src/lib/app-utils/element-animation.js +233 -0
- package/src/lib/app-utils/element-animation.js.map +1 -0
- package/src/lib/app-utils/element-hidden-style.d.ts +46 -0
- package/src/lib/app-utils/element-hidden-style.js +48 -0
- package/src/lib/app-utils/element-hidden-style.js.map +1 -0
- package/src/lib/app-utils/element-ui.d.ts +243 -0
- package/src/lib/app-utils/element-ui.js +459 -0
- package/src/lib/app-utils/element-ui.js.map +1 -0
- package/src/lib/app-utils/email-starter-policy.d.ts +160 -0
- package/src/lib/app-utils/email-starter-policy.js +327 -0
- package/src/lib/app-utils/email-starter-policy.js.map +1 -0
- package/src/lib/app-utils/email-topics.d.ts +310 -0
- package/src/lib/app-utils/email-topics.js +336 -0
- package/src/lib/app-utils/email-topics.js.map +1 -0
- package/src/lib/app-utils/enabled-plugins-context.d.ts +100 -0
- package/src/lib/app-utils/enabled-plugins-context.js +113 -0
- package/src/lib/app-utils/enabled-plugins-context.js.map +1 -0
- package/src/lib/app-utils/ensure-canvas-root.d.ts +38 -0
- package/src/lib/app-utils/ensure-canvas-root.js +61 -0
- package/src/lib/app-utils/ensure-canvas-root.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +199 -0
- package/src/lib/app-utils/entity-picker-context.js +93 -0
- package/src/lib/app-utils/entity-picker-context.js.map +1 -0
- package/src/lib/app-utils/error-beacon.d.ts +107 -0
- package/src/lib/app-utils/error-beacon.js +296 -0
- package/src/lib/app-utils/error-beacon.js.map +1 -0
- package/src/lib/app-utils/expand-repeatables.d.ts +172 -0
- package/src/lib/app-utils/expand-repeatables.js +299 -0
- package/src/lib/app-utils/expand-repeatables.js.map +1 -0
- package/src/lib/app-utils/foreign-script.d.ts +82 -0
- package/src/lib/app-utils/foreign-script.js +102 -0
- package/src/lib/app-utils/foreign-script.js.map +1 -0
- package/src/lib/app-utils/form-abuse-ceiling.d.ts +146 -0
- package/src/lib/app-utils/form-abuse-ceiling.js +158 -0
- package/src/lib/app-utils/form-abuse-ceiling.js.map +1 -0
- package/src/lib/app-utils/form-contract.d.ts +198 -0
- package/src/lib/app-utils/form-contract.js +282 -0
- package/src/lib/app-utils/form-contract.js.map +1 -0
- package/src/lib/app-utils/form-dataset-binding.d.ts +50 -0
- package/src/lib/app-utils/form-dataset-binding.js +74 -0
- package/src/lib/app-utils/form-dataset-binding.js.map +1 -0
- package/src/lib/app-utils/forms.d.ts +727 -0
- package/src/lib/app-utils/forms.js +777 -0
- package/src/lib/app-utils/forms.js.map +1 -0
- package/src/lib/app-utils/functions.d.ts +188 -0
- package/src/lib/app-utils/functions.js +529 -0
- package/src/lib/app-utils/functions.js.map +1 -0
- package/src/lib/app-utils/guarded-version-save.d.ts +91 -0
- package/src/lib/app-utils/guarded-version-save.js +131 -0
- package/src/lib/app-utils/guarded-version-save.js.map +1 -0
- package/src/lib/app-utils/health-report.d.ts +1766 -0
- package/src/lib/app-utils/health-report.js +2072 -0
- package/src/lib/app-utils/health-report.js.map +1 -0
- package/src/lib/app-utils/host-naming.d.ts +135 -0
- package/src/lib/app-utils/host-naming.js +188 -0
- package/src/lib/app-utils/host-naming.js.map +1 -0
- package/src/lib/app-utils/host-permissions.d.ts +76 -0
- package/src/lib/app-utils/host-permissions.js +119 -0
- package/src/lib/app-utils/host-permissions.js.map +1 -0
- package/src/lib/app-utils/host-tokens.d.ts +228 -0
- package/src/lib/app-utils/host-tokens.js +304 -0
- package/src/lib/app-utils/host-tokens.js.map +1 -0
- package/src/lib/app-utils/idp-profile.d.ts +83 -0
- package/src/lib/app-utils/idp-profile.js +268 -0
- package/src/lib/app-utils/idp-profile.js.map +1 -0
- package/src/lib/app-utils/image-upload-types.d.ts +49 -0
- package/src/lib/app-utils/image-upload-types.js +86 -0
- package/src/lib/app-utils/image-upload-types.js.map +1 -0
- package/src/lib/app-utils/index.d.ts +18 -0
- package/src/lib/app-utils/index.js +23 -0
- package/src/lib/app-utils/index.js.map +1 -0
- package/src/lib/app-utils/internal-traffic.d.ts +197 -0
- package/src/lib/app-utils/internal-traffic.js +288 -0
- package/src/lib/app-utils/internal-traffic.js.map +1 -0
- package/src/lib/app-utils/lineal-order.d.ts +62 -0
- package/src/lib/app-utils/lineal-order.js +65 -0
- package/src/lib/app-utils/lineal-order.js.map +1 -0
- package/src/lib/app-utils/link-references.d.ts +36 -0
- package/src/lib/app-utils/link-references.js +89 -0
- package/src/lib/app-utils/link-references.js.map +1 -0
- package/src/lib/app-utils/link-target-search-context.d.ts +123 -0
- package/src/lib/app-utils/link-target-search-context.js +221 -0
- package/src/lib/app-utils/link-target-search-context.js.map +1 -0
- package/src/lib/app-utils/list-assignment-policy.d.ts +169 -0
- package/src/lib/app-utils/list-assignment-policy.js +170 -0
- package/src/lib/app-utils/list-assignment-policy.js.map +1 -0
- package/src/lib/app-utils/list-import.d.ts +158 -0
- package/src/lib/app-utils/list-import.js +332 -0
- package/src/lib/app-utils/list-import.js.map +1 -0
- package/src/lib/app-utils/llms-txt.d.ts +104 -0
- package/src/lib/app-utils/llms-txt.js +144 -0
- package/src/lib/app-utils/llms-txt.js.map +1 -0
- package/src/lib/app-utils/local-storage-budget.d.ts +75 -0
- package/src/lib/app-utils/local-storage-budget.js +153 -0
- package/src/lib/app-utils/local-storage-budget.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +698 -0
- package/src/lib/app-utils/lockdown.js +929 -0
- package/src/lib/app-utils/lockdown.js.map +1 -0
- package/src/lib/app-utils/margin-utilization.d.ts +267 -0
- package/src/lib/app-utils/margin-utilization.js +351 -0
- package/src/lib/app-utils/margin-utilization.js.map +1 -0
- package/src/lib/app-utils/markdown-lite.d.ts +297 -0
- package/src/lib/app-utils/markdown-lite.js +705 -0
- package/src/lib/app-utils/markdown-lite.js.map +1 -0
- package/src/lib/app-utils/marketing-consent.d.ts +704 -0
- package/src/lib/app-utils/marketing-consent.js +783 -0
- package/src/lib/app-utils/marketing-consent.js.map +1 -0
- package/src/lib/app-utils/marketplace-listing-visibility.d.ts +120 -0
- package/src/lib/app-utils/marketplace-listing-visibility.js +139 -0
- package/src/lib/app-utils/marketplace-listing-visibility.js.map +1 -0
- package/src/lib/app-utils/marketplace-overrides.d.ts +271 -0
- package/src/lib/app-utils/marketplace-overrides.js +462 -0
- package/src/lib/app-utils/marketplace-overrides.js.map +1 -0
- package/src/lib/app-utils/marketplace-provenance.d.ts +150 -0
- package/src/lib/app-utils/marketplace-provenance.js +136 -0
- package/src/lib/app-utils/marketplace-provenance.js.map +1 -0
- package/src/lib/app-utils/marketplace-theme.d.ts +237 -0
- package/src/lib/app-utils/marketplace-theme.js +525 -0
- package/src/lib/app-utils/marketplace-theme.js.map +1 -0
- package/src/lib/app-utils/marketplace-update-state.d.ts +119 -0
- package/src/lib/app-utils/marketplace-update-state.js +132 -0
- package/src/lib/app-utils/marketplace-update-state.js.map +1 -0
- package/src/lib/app-utils/marketplace-verification.d.ts +95 -0
- package/src/lib/app-utils/marketplace-verification.js +81 -0
- package/src/lib/app-utils/marketplace-verification.js.map +1 -0
- package/src/lib/app-utils/measure-node-map.d.ts +76 -0
- package/src/lib/app-utils/measure-node-map.js +111 -0
- package/src/lib/app-utils/measure-node-map.js.map +1 -0
- package/src/lib/app-utils/media-alt.d.ts +55 -0
- package/src/lib/app-utils/media-alt.js +58 -0
- package/src/lib/app-utils/media-alt.js.map +1 -0
- package/src/lib/app-utils/media-asset-facts.d.ts +125 -0
- package/src/lib/app-utils/media-asset-facts.js +467 -0
- package/src/lib/app-utils/media-asset-facts.js.map +1 -0
- package/src/lib/app-utils/media-cdn-scope.d.ts +65 -0
- package/src/lib/app-utils/media-cdn-scope.js +75 -0
- package/src/lib/app-utils/media-cdn-scope.js.map +1 -0
- package/src/lib/app-utils/media-folders.d.ts +161 -0
- package/src/lib/app-utils/media-folders.js +230 -0
- package/src/lib/app-utils/media-folders.js.map +1 -0
- package/src/lib/app-utils/media-metadata.d.ts +277 -0
- package/src/lib/app-utils/media-metadata.js +388 -0
- package/src/lib/app-utils/media-metadata.js.map +1 -0
- package/src/lib/app-utils/media-picker-context.d.ts +93 -0
- package/src/lib/app-utils/media-picker-context.js +26 -0
- package/src/lib/app-utils/media-picker-context.js.map +1 -0
- package/src/lib/app-utils/media-quarantine.d.ts +247 -0
- package/src/lib/app-utils/media-quarantine.js +323 -0
- package/src/lib/app-utils/media-quarantine.js.map +1 -0
- package/src/lib/app-utils/media-ref.d.ts +661 -0
- package/src/lib/app-utils/media-ref.js +1055 -0
- package/src/lib/app-utils/media-ref.js.map +1 -0
- package/src/lib/app-utils/media-takedown-reach.d.ts +126 -0
- package/src/lib/app-utils/media-takedown-reach.js +143 -0
- package/src/lib/app-utils/media-takedown-reach.js.map +1 -0
- package/src/lib/app-utils/member-email-aliases.d.ts +180 -0
- package/src/lib/app-utils/member-email-aliases.js +217 -0
- package/src/lib/app-utils/member-email-aliases.js.map +1 -0
- package/src/lib/app-utils/merge-node-sx.d.ts +47 -0
- package/src/lib/app-utils/merge-node-sx.js +123 -0
- package/src/lib/app-utils/merge-node-sx.js.map +1 -0
- package/src/lib/app-utils/name-match.d.ts +155 -0
- package/src/lib/app-utils/name-match.js +175 -0
- package/src/lib/app-utils/name-match.js.map +1 -0
- package/src/lib/app-utils/name-search.d.ts +117 -0
- package/src/lib/app-utils/name-search.js +145 -0
- package/src/lib/app-utils/name-search.js.map +1 -0
- package/src/lib/app-utils/node-capabilities.d.ts +85 -0
- package/src/lib/app-utils/node-capabilities.js +88 -0
- package/src/lib/app-utils/node-capabilities.js.map +1 -0
- package/src/lib/app-utils/node-definition-sanitizer.d.ts +164 -0
- package/src/lib/app-utils/node-definition-sanitizer.js +402 -0
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -0
- package/src/lib/app-utils/node-identity.d.ts +47 -0
- package/src/lib/app-utils/node-identity.js +50 -0
- package/src/lib/app-utils/node-identity.js.map +1 -0
- package/src/lib/app-utils/node-interactions.d.ts +102 -0
- package/src/lib/app-utils/node-interactions.js +192 -0
- package/src/lib/app-utils/node-interactions.js.map +1 -0
- package/src/lib/app-utils/node-url-policy.d.ts +54 -0
- package/src/lib/app-utils/node-url-policy.js +72 -0
- package/src/lib/app-utils/node-url-policy.js.map +1 -0
- package/src/lib/app-utils/notifications.d.ts +71 -0
- package/src/lib/app-utils/notifications.js +101 -0
- package/src/lib/app-utils/notifications.js.map +1 -0
- package/src/lib/app-utils/onboarding-deep-link.d.ts +120 -0
- package/src/lib/app-utils/onboarding-deep-link.js +142 -0
- package/src/lib/app-utils/onboarding-deep-link.js.map +1 -0
- package/src/lib/app-utils/operator-identity.d.ts +207 -0
- package/src/lib/app-utils/operator-identity.js +186 -0
- package/src/lib/app-utils/operator-identity.js.map +1 -0
- package/src/lib/app-utils/org-billing-doc.d.ts +231 -0
- package/src/lib/app-utils/org-billing-doc.js +238 -0
- package/src/lib/app-utils/org-billing-doc.js.map +1 -0
- package/src/lib/app-utils/org-override-reason.d.ts +100 -0
- package/src/lib/app-utils/org-override-reason.js +116 -0
- package/src/lib/app-utils/org-override-reason.js.map +1 -0
- package/src/lib/app-utils/org-permissions.d.ts +194 -0
- package/src/lib/app-utils/org-permissions.js +357 -0
- package/src/lib/app-utils/org-permissions.js.map +1 -0
- package/src/lib/app-utils/org-roles.d.ts +72 -0
- package/src/lib/app-utils/org-roles.js +131 -0
- package/src/lib/app-utils/org-roles.js.map +1 -0
- package/src/lib/app-utils/organizations.d.ts +407 -0
- package/src/lib/app-utils/organizations.js +511 -0
- package/src/lib/app-utils/organizations.js.map +1 -0
- package/src/lib/app-utils/page-header-actions-context.d.ts +83 -0
- package/src/lib/app-utils/page-header-actions-context.js +58 -0
- package/src/lib/app-utils/page-header-actions-context.js.map +1 -0
- package/src/lib/app-utils/page-header-record-context.d.ts +80 -0
- package/src/lib/app-utils/page-header-record-context.js +64 -0
- package/src/lib/app-utils/page-header-record-context.js.map +1 -0
- package/src/lib/app-utils/page-markdown.d.ts +103 -0
- package/src/lib/app-utils/page-markdown.js +385 -0
- package/src/lib/app-utils/page-markdown.js.map +1 -0
- package/src/lib/app-utils/paid-media-source.d.ts +90 -0
- package/src/lib/app-utils/paid-media-source.js +184 -0
- package/src/lib/app-utils/paid-media-source.js.map +1 -0
- package/src/lib/app-utils/palette-sx.d.ts +34 -0
- package/src/lib/app-utils/palette-sx.js +150 -0
- package/src/lib/app-utils/palette-sx.js.map +1 -0
- package/src/lib/app-utils/password-policy.d.ts +47 -0
- package/src/lib/app-utils/password-policy.js +46 -0
- package/src/lib/app-utils/password-policy.js.map +1 -0
- package/src/lib/app-utils/payments-configured.d.ts +84 -0
- package/src/lib/app-utils/payments-configured.js +88 -0
- package/src/lib/app-utils/payments-configured.js.map +1 -0
- package/src/lib/app-utils/person-erasure.d.ts +126 -0
- package/src/lib/app-utils/person-erasure.js +121 -0
- package/src/lib/app-utils/person-erasure.js.map +1 -0
- package/src/lib/app-utils/person-key.d.ts +32 -0
- package/src/lib/app-utils/person-key.js +62 -0
- package/src/lib/app-utils/person-key.js.map +1 -0
- package/src/lib/app-utils/plan-entitlements.d.ts +2756 -0
- package/src/lib/app-utils/plan-entitlements.js +4085 -0
- package/src/lib/app-utils/plan-entitlements.js.map +1 -0
- package/src/lib/app-utils/platform-ad-conversions.d.ts +122 -0
- package/src/lib/app-utils/platform-ad-conversions.js +174 -0
- package/src/lib/app-utils/platform-ad-conversions.js.map +1 -0
- package/src/lib/app-utils/platform-advertising-tags.d.ts +139 -0
- package/src/lib/app-utils/platform-advertising-tags.js +203 -0
- package/src/lib/app-utils/platform-advertising-tags.js.map +1 -0
- package/src/lib/app-utils/platform-brand.d.ts +151 -0
- package/src/lib/app-utils/platform-brand.js +175 -0
- package/src/lib/app-utils/platform-brand.js.map +1 -0
- package/src/lib/app-utils/platform-consent-default.d.ts +142 -0
- package/src/lib/app-utils/platform-consent-default.js +295 -0
- package/src/lib/app-utils/platform-consent-default.js.map +1 -0
- package/src/lib/app-utils/platform-marketing-consent.d.ts +151 -0
- package/src/lib/app-utils/platform-marketing-consent.js +182 -0
- package/src/lib/app-utils/platform-marketing-consent.js.map +1 -0
- package/src/lib/app-utils/platform-marketing-host.d.ts +33 -0
- package/src/lib/app-utils/platform-marketing-host.js +97 -0
- package/src/lib/app-utils/platform-marketing-host.js.map +1 -0
- package/src/lib/app-utils/platform-visitor-consent.d.ts +252 -0
- package/src/lib/app-utils/platform-visitor-consent.js +533 -0
- package/src/lib/app-utils/platform-visitor-consent.js.map +1 -0
- package/src/lib/app-utils/plugin-api-cross-origin.d.ts +61 -0
- package/src/lib/app-utils/plugin-api-cross-origin.js +202 -0
- package/src/lib/app-utils/plugin-api-cross-origin.js.map +1 -0
- package/src/lib/app-utils/plugin-api-rate-limit.d.ts +192 -0
- package/src/lib/app-utils/plugin-api-rate-limit.js +270 -0
- package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -0
- package/src/lib/app-utils/plugin-artifact-path.d.ts +32 -0
- package/src/lib/app-utils/plugin-artifact-path.js +33 -0
- package/src/lib/app-utils/plugin-artifact-path.js.map +1 -0
- package/src/lib/app-utils/plugin-bundle-checks.d.ts +148 -0
- package/src/lib/app-utils/plugin-bundle-checks.js +989 -0
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -0
- package/src/lib/app-utils/plugin-manifest.d.ts +516 -0
- package/src/lib/app-utils/plugin-manifest.js +687 -0
- package/src/lib/app-utils/plugin-manifest.js.map +1 -0
- package/src/lib/app-utils/published-legal-pages.d.ts +71 -0
- package/src/lib/app-utils/published-legal-pages.js +90 -0
- package/src/lib/app-utils/published-legal-pages.js.map +1 -0
- package/src/lib/app-utils/publisher-agreement.d.ts +254 -0
- package/src/lib/app-utils/publisher-agreement.js +274 -0
- package/src/lib/app-utils/publisher-agreement.js.map +1 -0
- package/src/lib/app-utils/publisher-attestation.d.ts +99 -0
- package/src/lib/app-utils/publisher-attestation.js +158 -0
- package/src/lib/app-utils/publisher-attestation.js.map +1 -0
- package/src/lib/app-utils/recipient-email-render.d.ts +216 -0
- package/src/lib/app-utils/recipient-email-render.js +221 -0
- package/src/lib/app-utils/recipient-email-render.js.map +1 -0
- package/src/lib/app-utils/redispatch-caught-error.d.ts +38 -0
- package/src/lib/app-utils/redispatch-caught-error.js +46 -0
- package/src/lib/app-utils/redispatch-caught-error.js.map +1 -0
- package/src/lib/app-utils/registering-plugin.d.ts +19 -0
- package/src/lib/app-utils/registering-plugin.js +38 -0
- package/src/lib/app-utils/registering-plugin.js.map +1 -0
- package/src/lib/app-utils/release-flags.d.ts +283 -0
- package/src/lib/app-utils/release-flags.js +518 -0
- package/src/lib/app-utils/release-flags.js.map +1 -0
- package/src/lib/app-utils/repair-canvas-nodes.d.ts +130 -0
- package/src/lib/app-utils/repair-canvas-nodes.js +298 -0
- package/src/lib/app-utils/repair-canvas-nodes.js.map +1 -0
- package/src/lib/app-utils/repeat-infringer.d.ts +166 -0
- package/src/lib/app-utils/repeat-infringer.js +188 -0
- package/src/lib/app-utils/repeat-infringer.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +115 -0
- package/src/lib/app-utils/repeat-sources.js +86 -0
- package/src/lib/app-utils/repeat-sources.js.map +1 -0
- package/src/lib/app-utils/request-geo.d.ts +114 -0
- package/src/lib/app-utils/request-geo.js +247 -0
- package/src/lib/app-utils/request-geo.js.map +1 -0
- package/src/lib/app-utils/request-ip.d.ts +213 -0
- package/src/lib/app-utils/request-ip.js +344 -0
- package/src/lib/app-utils/request-ip.js.map +1 -0
- package/src/lib/app-utils/resolve-named-tokens.d.ts +26 -0
- package/src/lib/app-utils/resolve-named-tokens.js +59 -0
- package/src/lib/app-utils/resolve-named-tokens.js.map +1 -0
- package/src/lib/app-utils/responsive-typography-sx.d.ts +77 -0
- package/src/lib/app-utils/responsive-typography-sx.js +122 -0
- package/src/lib/app-utils/responsive-typography-sx.js.map +1 -0
- package/src/lib/app-utils/reusable-component-keys.d.ts +135 -0
- package/src/lib/app-utils/reusable-component-keys.js +140 -0
- package/src/lib/app-utils/reusable-component-keys.js.map +1 -0
- package/src/lib/app-utils/reusable-prop-values.d.ts +199 -0
- package/src/lib/app-utils/reusable-prop-values.js +494 -0
- package/src/lib/app-utils/reusable-prop-values.js.map +1 -0
- package/src/lib/app-utils/safe-json-ld.d.ts +29 -0
- package/src/lib/app-utils/safe-json-ld.js +37 -0
- package/src/lib/app-utils/safe-json-ld.js.map +1 -0
- package/src/lib/app-utils/sanitize-svg.d.ts +55 -0
- package/src/lib/app-utils/sanitize-svg.js +460 -0
- package/src/lib/app-utils/sanitize-svg.js.map +1 -0
- package/src/lib/app-utils/scheme-sx.d.ts +84 -0
- package/src/lib/app-utils/scheme-sx.js +148 -0
- package/src/lib/app-utils/scheme-sx.js.map +1 -0
- package/src/lib/app-utils/scope-tokens.d.ts +249 -0
- package/src/lib/app-utils/scope-tokens.js +331 -0
- package/src/lib/app-utils/scope-tokens.js.map +1 -0
- package/src/lib/app-utils/screen-link-context-value.d.ts +57 -0
- package/src/lib/app-utils/screen-link-context-value.js +43 -0
- package/src/lib/app-utils/screen-link-context-value.js.map +1 -0
- package/src/lib/app-utils/screen-link-context.d.ts +185 -0
- package/src/lib/app-utils/screen-link-context.js +304 -0
- package/src/lib/app-utils/screen-link-context.js.map +1 -0
- package/src/lib/app-utils/screen-link-value.d.ts +269 -0
- package/src/lib/app-utils/screen-link-value.js +384 -0
- package/src/lib/app-utils/screen-link-value.js.map +1 -0
- package/src/lib/app-utils/screen-route.d.ts +669 -0
- package/src/lib/app-utils/screen-route.js +751 -0
- package/src/lib/app-utils/screen-route.js.map +1 -0
- package/src/lib/app-utils/screen-seo-fields.d.ts +37 -0
- package/src/lib/app-utils/screen-seo-fields.js +42 -0
- package/src/lib/app-utils/screen-seo-fields.js.map +1 -0
- package/src/lib/app-utils/search-indexing.d.ts +162 -0
- package/src/lib/app-utils/search-indexing.js +207 -0
- package/src/lib/app-utils/search-indexing.js.map +1 -0
- package/src/lib/app-utils/seo-listing-fields.d.ts +70 -0
- package/src/lib/app-utils/seo-listing-fields.js +75 -0
- package/src/lib/app-utils/seo-listing-fields.js.map +1 -0
- package/src/lib/app-utils/seo-locale.d.ts +119 -0
- package/src/lib/app-utils/seo-locale.js +133 -0
- package/src/lib/app-utils/seo-locale.js.map +1 -0
- package/src/lib/app-utils/seo-title-variables.d.ts +149 -0
- package/src/lib/app-utils/seo-title-variables.js +190 -0
- package/src/lib/app-utils/seo-title-variables.js.map +1 -0
- package/src/lib/app-utils/seo-title.d.ts +70 -0
- package/src/lib/app-utils/seo-title.js +117 -0
- package/src/lib/app-utils/seo-title.js.map +1 -0
- package/src/lib/app-utils/server.d.ts +174 -0
- package/src/lib/app-utils/server.js +314 -0
- package/src/lib/app-utils/server.js.map +1 -0
- package/src/lib/app-utils/site-context.d.ts +106 -0
- package/src/lib/app-utils/site-context.js +100 -0
- package/src/lib/app-utils/site-context.js.map +1 -0
- package/src/lib/app-utils/sitemap.d.ts +117 -0
- package/src/lib/app-utils/sitemap.js +183 -0
- package/src/lib/app-utils/sitemap.js.map +1 -0
- package/src/lib/app-utils/social-image.d.ts +112 -0
- package/src/lib/app-utils/social-image.js +191 -0
- package/src/lib/app-utils/social-image.js.map +1 -0
- package/src/lib/app-utils/starter-templates.d.ts +99 -0
- package/src/lib/app-utils/starter-templates.js +579 -0
- package/src/lib/app-utils/starter-templates.js.map +1 -0
- package/src/lib/app-utils/stored-nodes.d.ts +120 -0
- package/src/lib/app-utils/stored-nodes.js +174 -0
- package/src/lib/app-utils/stored-nodes.js.map +1 -0
- package/src/lib/app-utils/strip-undefined.d.ts +59 -0
- package/src/lib/app-utils/strip-undefined.js +95 -0
- package/src/lib/app-utils/strip-undefined.js.map +1 -0
- package/src/lib/app-utils/stripe-deployment-mode.d.ts +47 -0
- package/src/lib/app-utils/stripe-deployment-mode.js +70 -0
- package/src/lib/app-utils/stripe-deployment-mode.js.map +1 -0
- package/src/lib/app-utils/support-tiers.d.ts +108 -0
- package/src/lib/app-utils/support-tiers.js +201 -0
- package/src/lib/app-utils/support-tiers.js.map +1 -0
- package/src/lib/app-utils/timezone-geo-hint.d.ts +36 -0
- package/src/lib/app-utils/timezone-geo-hint.js +180 -0
- package/src/lib/app-utils/timezone-geo-hint.js.map +1 -0
- package/src/lib/app-utils/upload-cors.d.ts +155 -0
- package/src/lib/app-utils/upload-cors.js +254 -0
- package/src/lib/app-utils/upload-cors.js.map +1 -0
- package/src/lib/app-utils/upload-inspection.d.ts +159 -0
- package/src/lib/app-utils/upload-inspection.js +539 -0
- package/src/lib/app-utils/upload-inspection.js.map +1 -0
- package/src/lib/app-utils/url-slug.d.ts +40 -0
- package/src/lib/app-utils/url-slug.js +42 -0
- package/src/lib/app-utils/url-slug.js.map +1 -0
- package/src/lib/app-utils/usage-budget.d.ts +429 -0
- package/src/lib/app-utils/usage-budget.js +421 -0
- package/src/lib/app-utils/usage-budget.js.map +1 -0
- package/src/lib/app-utils/variables.d.ts +96 -0
- package/src/lib/app-utils/variables.js +233 -0
- package/src/lib/app-utils/variables.js.map +1 -0
- package/src/lib/app-utils/video-asset-facts.d.ts +61 -0
- package/src/lib/app-utils/video-asset-facts.js +70 -0
- package/src/lib/app-utils/video-asset-facts.js.map +1 -0
- package/src/lib/app-utils/video-object.d.ts +89 -0
- package/src/lib/app-utils/video-object.js +245 -0
- package/src/lib/app-utils/video-object.js.map +1 -0
- package/src/lib/app-utils/visitor-consent.d.ts +757 -0
- package/src/lib/app-utils/visitor-consent.js +981 -0
- package/src/lib/app-utils/visitor-consent.js.map +1 -0
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +223 -0
- package/src/lib/app-utils/visitor-record-ceiling.js +215 -0
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -0
- package/src/lib/app-utils/web-vitals-rum.d.ts +53 -0
- package/src/lib/app-utils/web-vitals-rum.js +278 -0
- package/src/lib/app-utils/web-vitals-rum.js.map +1 -0
- package/src/lib/app-utils/webhook-delivery.d.ts +324 -0
- package/src/lib/app-utils/webhook-delivery.js +413 -0
- package/src/lib/app-utils/webhook-delivery.js.map +1 -0
- package/src/lib/app-utils/where-used.d.ts +57 -0
- package/src/lib/app-utils/where-used.js +59 -0
- package/src/lib/app-utils/where-used.js.map +1 -0
- package/src/lib/app-utils/wistia-embed.d.ts +86 -0
- package/src/lib/app-utils/wistia-embed.js +119 -0
- package/src/lib/app-utils/wistia-embed.js.map +1 -0
- package/src/lib/app-utils/workflows.d.ts +136 -0
- package/src/lib/app-utils/workflows.js +346 -0
- package/src/lib/app-utils/workflows.js.map +1 -0
- package/src/lib/canvas-manager/canvas-manager-tree.d.ts +109 -0
- package/src/lib/canvas-manager/canvas-manager-tree.js +139 -0
- package/src/lib/canvas-manager/canvas-manager-tree.js.map +1 -0
- package/src/lib/canvas-manager/canvas-manager.d.ts +549 -0
- package/src/lib/canvas-manager/canvas-manager.js +1311 -0
- package/src/lib/canvas-manager/canvas-manager.js.map +1 -0
- package/src/lib/canvas-manager/index.d.ts +20 -0
- package/src/lib/canvas-manager/index.js +21 -0
- package/src/lib/canvas-manager/index.js.map +1 -0
- package/src/lib/canvas-manager/stores/index.d.ts +17 -0
- package/src/lib/canvas-manager/stores/index.js +18 -0
- package/src/lib/canvas-manager/stores/index.js.map +1 -0
- package/src/lib/canvas-manager/stores/node.store.d.ts +30 -0
- package/src/lib/canvas-manager/stores/node.store.js +45 -0
- package/src/lib/canvas-manager/stores/node.store.js.map +1 -0
- package/src/lib/components-manager/components-manager.d.ts +56 -0
- package/src/lib/components-manager/components-manager.js +191 -0
- package/src/lib/components-manager/components-manager.js.map +1 -0
- package/src/lib/components-manager/index.d.ts +18 -0
- package/src/lib/components-manager/index.js +19 -0
- package/src/lib/components-manager/index.js.map +1 -0
- package/src/lib/emit-manager/emit-manager.d.ts +56 -0
- package/src/lib/emit-manager/emit-manager.js +60 -0
- package/src/lib/emit-manager/emit-manager.js.map +1 -0
- package/src/lib/emit-manager/index.d.ts +18 -0
- package/src/lib/emit-manager/index.js +19 -0
- package/src/lib/emit-manager/index.js.map +1 -0
- package/src/lib/foundation/constants/_internal.d.ts +18 -0
- package/src/lib/foundation/constants/_internal.js +62 -0
- package/src/lib/foundation/constants/_internal.js.map +1 -0
- package/src/lib/foundation/constants/app.d.ts +23 -0
- package/src/lib/foundation/constants/app.js +25 -0
- package/src/lib/foundation/constants/app.js.map +1 -0
- package/src/lib/foundation/constants/canvas.d.ts +18 -0
- package/src/lib/foundation/constants/canvas.js +18 -0
- package/src/lib/foundation/constants/canvas.js.map +1 -0
- package/src/lib/foundation/constants/components.d.ts +54 -0
- package/src/lib/foundation/constants/components.js +61 -0
- package/src/lib/foundation/constants/components.js.map +1 -0
- package/src/lib/foundation/constants/shared.d.ts +50 -0
- package/src/lib/foundation/constants/shared.js +49 -0
- package/src/lib/foundation/constants/shared.js.map +1 -0
- package/src/lib/foundation/constants/symbol.d.ts +63 -0
- package/src/lib/foundation/constants/symbol.js +75 -0
- package/src/lib/foundation/constants/symbol.js.map +1 -0
- package/src/lib/foundation/definitions/components.types.d.ts +372 -0
- package/src/lib/foundation/definitions/components.types.js +180 -0
- package/src/lib/foundation/definitions/components.types.js.map +1 -0
- package/src/lib/foundation/definitions/contact.types.d.ts +73 -0
- package/src/lib/foundation/definitions/contact.types.js +107 -0
- package/src/lib/foundation/definitions/contact.types.js.map +1 -0
- package/src/lib/foundation/definitions/host-content-collections.d.ts +137 -0
- package/src/lib/foundation/definitions/host-content-collections.js +219 -0
- package/src/lib/foundation/definitions/host-content-collections.js.map +1 -0
- package/src/lib/foundation/definitions/org-billing.types.d.ts +1361 -0
- package/src/lib/foundation/definitions/org-billing.types.js +77 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -0
- package/src/lib/foundation/definitions/organization.types.d.ts +307 -0
- package/src/lib/foundation/definitions/organization.types.js +42 -0
- package/src/lib/foundation/definitions/organization.types.js.map +1 -0
- package/src/lib/foundation/definitions/platform.types.d.ts +1326 -0
- package/src/lib/foundation/definitions/platform.types.js +124 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -0
- package/src/lib/foundation/definitions/property-kinds.d.ts +189 -0
- package/src/lib/foundation/definitions/property-kinds.js +565 -0
- package/src/lib/foundation/definitions/property-kinds.js.map +1 -0
- package/src/lib/foundation/definitions/shared.d.ts +23 -0
- package/src/lib/foundation/definitions/shared.js +18 -0
- package/src/lib/foundation/definitions/shared.js.map +1 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +249 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +499 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -0
- package/src/lib/foundation/index.d.ts +32 -0
- package/src/lib/foundation/index.js +67 -0
- package/src/lib/foundation/index.js.map +1 -0
- package/src/lib/lifecycle.d.ts +24 -0
- package/src/lib/lifecycle.js +39 -0
- package/src/lib/lifecycle.js.map +1 -0
- package/src/lib/mst/common.d.ts +17 -0
- package/src/lib/mst/common.js +23 -0
- package/src/lib/mst/common.js.map +1 -0
- package/src/lib/mst/index.d.ts +19 -0
- package/src/lib/mst/index.js +20 -0
- package/src/lib/mst/index.js.map +1 -0
- package/src/lib/mst/json.d.ts +22 -0
- package/src/lib/mst/json.js +23 -0
- package/src/lib/mst/json.js.map +1 -0
- package/src/lib/mst/types.d.ts +20 -0
- package/src/lib/mst/types.js +18 -0
- package/src/lib/mst/types.js.map +1 -0
- package/src/lib/plugin-manager/billing-webhook-hooks.d.ts +77 -0
- package/src/lib/plugin-manager/billing-webhook-hooks.js +55 -0
- package/src/lib/plugin-manager/billing-webhook-hooks.js.map +1 -0
- package/src/lib/plugin-manager/commerce-zone-props.d.ts +172 -0
- package/src/lib/plugin-manager/commerce-zone-props.js +31 -0
- package/src/lib/plugin-manager/commerce-zone-props.js.map +1 -0
- package/src/lib/plugin-manager/custom-fields.d.ts +60 -0
- package/src/lib/plugin-manager/custom-fields.js +39 -0
- package/src/lib/plugin-manager/custom-fields.js.map +1 -0
- package/src/lib/plugin-manager/editor-sessions.d.ts +109 -0
- package/src/lib/plugin-manager/editor-sessions.js +137 -0
- package/src/lib/plugin-manager/editor-sessions.js.map +1 -0
- package/src/lib/plugin-manager/enabled-plugins.d.ts +438 -0
- package/src/lib/plugin-manager/enabled-plugins.js +403 -0
- package/src/lib/plugin-manager/enabled-plugins.js.map +1 -0
- package/src/lib/plugin-manager/feature-plugins.d.ts +1314 -0
- package/src/lib/plugin-manager/feature-plugins.js +609 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +11 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +149 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -0
- package/src/lib/plugin-manager/index.d.ts +42 -0
- package/src/lib/plugin-manager/index.js +46 -0
- package/src/lib/plugin-manager/index.js.map +1 -0
- package/src/lib/plugin-manager/install-presets.d.ts +93 -0
- package/src/lib/plugin-manager/install-presets.js +86 -0
- package/src/lib/plugin-manager/install-presets.js.map +1 -0
- package/src/lib/plugin-manager/media-delivery-provider.d.ts +124 -0
- package/src/lib/plugin-manager/media-delivery-provider.js +53 -0
- package/src/lib/plugin-manager/media-delivery-provider.js.map +1 -0
- package/src/lib/plugin-manager/order-fulfilment.d.ts +142 -0
- package/src/lib/plugin-manager/order-fulfilment.js +104 -0
- package/src/lib/plugin-manager/order-fulfilment.js.map +1 -0
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +122 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +140 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -0
- package/src/lib/plugin-manager/plugin-bridge.d.ts +98 -0
- package/src/lib/plugin-manager/plugin-bridge.js +154 -0
- package/src/lib/plugin-manager/plugin-bridge.js.map +1 -0
- package/src/lib/plugin-manager/plugin-config.d.ts +148 -0
- package/src/lib/plugin-manager/plugin-config.js +228 -0
- package/src/lib/plugin-manager/plugin-config.js.map +1 -0
- package/src/lib/plugin-manager/plugin-console-crons.d.ts +139 -0
- package/src/lib/plugin-manager/plugin-console-crons.js +177 -0
- package/src/lib/plugin-manager/plugin-console-crons.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +220 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js +89 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contributions.d.ts +244 -0
- package/src/lib/plugin-manager/plugin-contributions.js +283 -0
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlement-keys.d.ts +169 -0
- package/src/lib/plugin-manager/plugin-entitlement-keys.js +84 -0
- package/src/lib/plugin-manager/plugin-entitlement-keys.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +181 -0
- package/src/lib/plugin-manager/plugin-entitlements.js +188 -0
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -0
- package/src/lib/plugin-manager/plugin-events.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-events.js +79 -0
- package/src/lib/plugin-manager/plugin-events.js.map +1 -0
- package/src/lib/plugin-manager/plugin-figures.d.ts +251 -0
- package/src/lib/plugin-manager/plugin-figures.js +252 -0
- package/src/lib/plugin-manager/plugin-figures.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +146 -0
- package/src/lib/plugin-manager/plugin-host-collections.js +116 -0
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +130 -0
- package/src/lib/plugin-manager/plugin-jobs.js +182 -0
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -0
- package/src/lib/plugin-manager/plugin-loader.d.ts +86 -0
- package/src/lib/plugin-manager/plugin-loader.js +223 -0
- package/src/lib/plugin-manager/plugin-loader.js.map +1 -0
- package/src/lib/plugin-manager/plugin-manager.d.ts +95 -0
- package/src/lib/plugin-manager/plugin-manager.js +343 -0
- package/src/lib/plugin-manager/plugin-manager.js.map +1 -0
- package/src/lib/plugin-manager/plugin-metered-lines.d.ts +97 -0
- package/src/lib/plugin-manager/plugin-metered-lines.js +123 -0
- package/src/lib/plugin-manager/plugin-metered-lines.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +52 -0
- package/src/lib/plugin-manager/plugin-org-erasure.js +100 -0
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -0
- package/src/lib/plugin-manager/plugin-permissions.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-permissions.js +77 -0
- package/src/lib/plugin-manager/plugin-permissions.js.map +1 -0
- package/src/lib/plugin-manager/plugin-person-erasure.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-person-erasure.js +109 -0
- package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-cards.d.ts +99 -0
- package/src/lib/plugin-manager/plugin-record-cards.js +59 -0
- package/src/lib/plugin-manager/plugin-record-cards.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-facts.d.ts +111 -0
- package/src/lib/plugin-manager/plugin-record-facts.js +51 -0
- package/src/lib/plugin-manager/plugin-record-facts.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-routes.d.ts +129 -0
- package/src/lib/plugin-manager/plugin-record-routes.js +93 -0
- package/src/lib/plugin-manager/plugin-record-routes.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +147 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js +39 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -0
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +137 -0
- package/src/lib/plugin-manager/plugin-resource-drafts.js +51 -0
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -0
- package/src/lib/plugin-manager/plugin-services.d.ts +94 -0
- package/src/lib/plugin-manager/plugin-services.js +153 -0
- package/src/lib/plugin-manager/plugin-services.js.map +1 -0
- package/src/lib/plugin-manager/plugin-styles-ui.d.ts +71 -0
- package/src/lib/plugin-manager/plugin-styles-ui.js +80 -0
- package/src/lib/plugin-manager/plugin-styles-ui.js.map +1 -0
- package/src/lib/plugin-manager/plugin-styles.d.ts +176 -0
- package/src/lib/plugin-manager/plugin-styles.js +186 -0
- package/src/lib/plugin-manager/plugin-styles.js.map +1 -0
- package/src/lib/plugin-manager/plugin-subprocessors.d.ts +135 -0
- package/src/lib/plugin-manager/plugin-subprocessors.js +129 -0
- package/src/lib/plugin-manager/plugin-subprocessors.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +84 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js +39 -0
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -0
- package/src/lib/plugin-manager/plugin-user-erasure.d.ts +53 -0
- package/src/lib/plugin-manager/plugin-user-erasure.js +84 -0
- package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -0
- package/src/lib/plugin-manager/plugin-zones.d.ts +121 -0
- package/src/lib/plugin-manager/plugin-zones.js +74 -0
- package/src/lib/plugin-manager/plugin-zones.js.map +1 -0
- package/src/lib/plugin-manager/realm-plugins.d.ts +112 -0
- package/src/lib/plugin-manager/realm-plugins.js +196 -0
- package/src/lib/plugin-manager/realm-plugins.js.map +1 -0
- package/src/lib/plugin-manager/realm-server.d.ts +54 -0
- package/src/lib/plugin-manager/realm-server.js +88 -0
- package/src/lib/plugin-manager/realm-server.js.map +1 -0
- package/src/lib/plugin-manager/record-zone-props.d.ts +126 -0
- package/src/lib/plugin-manager/record-zone-props.js +31 -0
- package/src/lib/plugin-manager/record-zone-props.js.map +1 -0
- package/src/lib/plugin-manager/site-page-hooks.d.ts +102 -0
- package/src/lib/plugin-manager/site-page-hooks.js +114 -0
- package/src/lib/plugin-manager/site-page-hooks.js.map +1 -0
- package/src/lib/plugin-manager/site-runtime.d.ts +57 -0
- package/src/lib/plugin-manager/site-runtime.js +25 -0
- package/src/lib/plugin-manager/site-runtime.js.map +1 -0
- package/src/lib/plugin-manager/usage-alert-contributors.d.ts +115 -0
- package/src/lib/plugin-manager/usage-alert-contributors.js +106 -0
- package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -0
- package/src/lib/types/ast.d.ts +199 -0
- package/src/lib/types/ast.js +20 -0
- package/src/lib/types/ast.js.map +1 -0
- package/src/lib/types/index.d.ts +20 -0
- package/src/lib/types/index.js +21 -0
- package/src/lib/types/index.js.map +1 -0
- package/src/lib/types/nodes.d.ts +608 -0
- package/src/lib/types/nodes.js +28 -0
- package/src/lib/types/nodes.js.map +1 -0
- package/src/lib/types/routing.d.ts +17 -0
- package/src/lib/types/routing.js +18 -0
- package/src/lib/types/routing.js.map +1 -0
- package/src/lib/types/screen.d.ts +23 -0
- package/src/lib/types/screen.js +18 -0
- package/src/lib/types/screen.js.map +1 -0
- package/src/lib/utils.d.ts +25 -0
- package/src/lib/utils.js +42 -0
- package/src/lib/utils.js.map +1 -0
- package/src/server.d.ts +37 -0
- package/src/server.js +55 -0
- package/src/server.js.map +1 -0
|
@@ -0,0 +1,1766 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Shared shape for the `/api/health` endpoints (AGL-1102).
|
|
19
|
+
*
|
|
20
|
+
* Both the console and the tenant runtime answer this, and they must answer it
|
|
21
|
+
* IDENTICALLY — an uptime series is only comparable across services if the
|
|
22
|
+
* status codes and the field names agree. Two near-identical route files would
|
|
23
|
+
* have drifted the first time one of them was touched.
|
|
24
|
+
*
|
|
25
|
+
* The rules encoded here, each of which is a way health checks routinely lie:
|
|
26
|
+
*
|
|
27
|
+
* **The status code is the contract.** Most uptime monitors read nothing else,
|
|
28
|
+
* so a degraded dependency has to be a 503. A 200 whose body says "degraded"
|
|
29
|
+
* is a signal nobody receives.
|
|
30
|
+
*
|
|
31
|
+
* **Never cacheable.** A cached health check returns 200 from the edge while
|
|
32
|
+
* the origin is on fire, and the graph stays green straight through the
|
|
33
|
+
* outage. Three separate caches have faked a green check in this repo already,
|
|
34
|
+
* so this sets `no-store` on every response including the failures — an error
|
|
35
|
+
* response is the one you least want served from a cache.
|
|
36
|
+
*
|
|
37
|
+
* **No internals in the body.** It is public and unauthenticated, so a failing
|
|
38
|
+
* check reports a CODE and never the underlying message, which can carry
|
|
39
|
+
* project ids, paths or credentials fragments.
|
|
40
|
+
*/
|
|
41
|
+
export interface HealthCheck {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
/** How long the dependency took. The number that turns into a latency graph. */
|
|
44
|
+
ms: number;
|
|
45
|
+
/** A stable code on failure — never a raw error message. */
|
|
46
|
+
code?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface HealthReport {
|
|
49
|
+
service: string;
|
|
50
|
+
checks: Record<string, HealthCheck>;
|
|
51
|
+
commit?: string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Which VERSION of the platform answered (AGL-2091).
|
|
54
|
+
*
|
|
55
|
+
* `commit` alone could only ever be answered on Aglyn's own cloud, so a
|
|
56
|
+
* self-host operator had nothing to quote in a bug report and no way to ask
|
|
57
|
+
* whether a fix had reached them. This is the field that answers it, and
|
|
58
|
+
* unlike the commit it needs no operator configuration —
|
|
59
|
+
* `platformVersion()` reads the number the build inlined from package.json.
|
|
60
|
+
*/
|
|
61
|
+
version?: string | null;
|
|
62
|
+
environment?: string | null;
|
|
63
|
+
region?: string | null;
|
|
64
|
+
/** Injected so the body is deterministic in tests. */
|
|
65
|
+
at?: string;
|
|
66
|
+
}
|
|
67
|
+
export declare const HEALTH_NO_STORE = "no-store, no-cache, must-revalidate, max-age=0";
|
|
68
|
+
/** Degraded if ANY check failed. There is no partial credit for uptime. */
|
|
69
|
+
export declare function healthStatus(checks: Record<string, HealthCheck>): 'ok' | 'degraded';
|
|
70
|
+
export declare function healthBody(report: HealthReport): Record<string, unknown>;
|
|
71
|
+
export declare function healthHeaders(status: 'ok' | 'degraded'): Record<string, string>;
|
|
72
|
+
export declare function healthHttpStatus(status: 'ok' | 'degraded'): number;
|
|
73
|
+
/**
|
|
74
|
+
* A HEAD response for a health endpoint: the SAME status and headers its GET
|
|
75
|
+
* would return, with no body (AGL-1148).
|
|
76
|
+
*
|
|
77
|
+
* ## Why this exists
|
|
78
|
+
*
|
|
79
|
+
* Every health route used to answer HEAD with a hardcoded `200` and the
|
|
80
|
+
* comment "cheap liveness for monitors that only issue HEAD. Touches
|
|
81
|
+
* nothing." Touching nothing is precisely the problem: **a HEAD that knows
|
|
82
|
+
* nothing is a health check that cannot go red.** A monitor configured with
|
|
83
|
+
* HEAD — which several uptime providers use by default — would have reported
|
|
84
|
+
* green through any outage that left the function able to boot, which is most
|
|
85
|
+
* of them. `/api/health/crons` sat at 503 for fifty-one hours; a HEAD monitor
|
|
86
|
+
* pointed at it would have agreed with the green board the whole time.
|
|
87
|
+
*
|
|
88
|
+
* It also violated the contract this file opens with. "The status code is the
|
|
89
|
+
* contract" cannot hold for one method and not another, and HTTP requires a
|
|
90
|
+
* HEAD response to carry the same status and headers its GET would.
|
|
91
|
+
*
|
|
92
|
+
* ## The cost argument, which is why this is safe
|
|
93
|
+
*
|
|
94
|
+
* Delegating to GET means HEAD runs the dependency probe. Every route
|
|
95
|
+
* memoizes that probe (`memoizeWithTtl`, 15s on the root and 5 minutes on the
|
|
96
|
+
* subsystems) explicitly so a public unauthenticated endpoint cannot be made
|
|
97
|
+
* to read in a loop — the same bound that already lets a fifteen-minute probe
|
|
98
|
+
* hit seven endpoints for nearly nothing. HEAD now costs exactly what GET
|
|
99
|
+
* costs, which is a memo lookup between refreshes, and it serializes no body.
|
|
100
|
+
*
|
|
101
|
+
* Headers are carried over rather than rebuilt so `Cache-Control: no-store`,
|
|
102
|
+
* the open CORS origin and `Retry-After` reach a HEAD client identically — a
|
|
103
|
+
* HEAD response that was cacheable while its GET was not would be the same
|
|
104
|
+
* lie one layer down.
|
|
105
|
+
*/
|
|
106
|
+
export declare function healthHeadOf(get: () => Promise<Response>): Promise<Response>;
|
|
107
|
+
/**
|
|
108
|
+
* Firestore backup state, reduced to a health verdict (AGL-1490, AGL-1502).
|
|
109
|
+
*
|
|
110
|
+
* The 2026-08-02 backup sat at `NOT_AVAILABLE` for eleven days and nothing
|
|
111
|
+
* noticed — the schedule ran, the API reported the failure, and no one was
|
|
112
|
+
* listening. This turns "is the newest backup real and recent" into the same
|
|
113
|
+
* 200/503 contract the other health checks speak, so the existing uptime
|
|
114
|
+
* probe + alert path is the listener.
|
|
115
|
+
*
|
|
116
|
+
* Pure on purpose: the route fetches, this decides, the spec exercises every
|
|
117
|
+
* branch without a network.
|
|
118
|
+
*/
|
|
119
|
+
export interface BackupSnapshot {
|
|
120
|
+
/** `READY` | `CREATING` | `NOT_AVAILABLE` (or whatever the API adds next). */
|
|
121
|
+
state?: string;
|
|
122
|
+
/** ISO timestamp of the data the backup captured. */
|
|
123
|
+
snapshotTime?: string;
|
|
124
|
+
}
|
|
125
|
+
export interface BackupsCheck extends HealthCheck {
|
|
126
|
+
/**
|
|
127
|
+
* Histogram by state — `{ READY: 1, NOT_AVAILABLE: 1 }`. Counts only: the
|
|
128
|
+
* endpoint is public, and backup ids or resource paths have no business in
|
|
129
|
+
* it. Counts are enough to know WHAT is wrong before opening gcloud.
|
|
130
|
+
*/
|
|
131
|
+
states: Record<string, number>;
|
|
132
|
+
/** Age of the newest READY backup, in days. Null when there is none. */
|
|
133
|
+
newestReadyAgeDays: number | null;
|
|
134
|
+
/**
|
|
135
|
+
* Present, and always `false`, ONLY in the third state (AGL-1843): the
|
|
136
|
+
* check could not determine whether backups are healthy. Omitted entirely
|
|
137
|
+
* from a real verdict, exactly as `code` is omitted from a healthy one, so
|
|
138
|
+
* `ok: true` with no `determinate` key means "measured, and fine".
|
|
139
|
+
*
|
|
140
|
+
* An indeterminate check reports `ok: true` and therefore does not page.
|
|
141
|
+
* That is deliberate and it is bounded — see the escalation note in
|
|
142
|
+
* `backupsHealth`.
|
|
143
|
+
*/
|
|
144
|
+
determinate?: false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Weekly Sunday schedule + one day of slack. A healthy cadence never exceeds
|
|
148
|
+
* 7; day 8 means the last run produced nothing usable.
|
|
149
|
+
*/
|
|
150
|
+
export declare const MAX_BACKUP_AGE_DAYS = 8;
|
|
151
|
+
/**
|
|
152
|
+
* `backups` is the listing, or `null` when it could not be read at all.
|
|
153
|
+
*
|
|
154
|
+
* `options.unreachable` is `ListBackupsResponse.unreachable` — the locations
|
|
155
|
+
* the API could not reach. Non-empty means the listing is a PARTIAL result
|
|
156
|
+
* set. `options.code` is the code to report when `backups` is `null`.
|
|
157
|
+
*/
|
|
158
|
+
export declare function backupsHealth(backups: BackupSnapshot[] | null, ms: number, now?: number, options?: {
|
|
159
|
+
unreachable?: string[];
|
|
160
|
+
code?: string;
|
|
161
|
+
}): BackupsCheck;
|
|
162
|
+
/**
|
|
163
|
+
* GCS export freshness, reduced to a health verdict (AGL-1843).
|
|
164
|
+
*
|
|
165
|
+
* Exports are NOT managed backups — they are the independent copy that exists
|
|
166
|
+
* BECAUSE the managed backups proved unreliable (every backup this project
|
|
167
|
+
* took flipped `READY` → `NOT_AVAILABLE` at roughly one week old, AGL-1843).
|
|
168
|
+
* A weekly `exportDocuments` run writes portable snapshot files to a GCS
|
|
169
|
+
* bucket, and this check watches that the newest one is real and recent, so
|
|
170
|
+
* a managed-backup flip no longer means "one copy only". The two are kept as
|
|
171
|
+
* SEPARATE checks in the same endpoint precisely so the body says which layer
|
|
172
|
+
* is degraded rather than blending them into one dishonest verdict.
|
|
173
|
+
*
|
|
174
|
+
* The input is the list of `*.overall_export_metadata` completion markers —
|
|
175
|
+
* Firestore writes exactly one per FINISHED export, so the marker's creation
|
|
176
|
+
* time is the moment the export completed, not the moment it started. An
|
|
177
|
+
* export that hangs forever never produces a marker and fails the age rule,
|
|
178
|
+
* the same trick `backupsHealth` plays with `CREATING`.
|
|
179
|
+
*
|
|
180
|
+
* Pure on purpose, like its siblings: the route lists, this decides, the
|
|
181
|
+
* spec exercises every branch without a network.
|
|
182
|
+
*/
|
|
183
|
+
export interface ExportMarker {
|
|
184
|
+
/** ISO timestamp the completion marker was written — when the export FINISHED. */
|
|
185
|
+
timeCreated?: string;
|
|
186
|
+
}
|
|
187
|
+
export interface ExportsCheck extends HealthCheck {
|
|
188
|
+
/**
|
|
189
|
+
* Completed exports currently retained. A COUNT only — the endpoint is
|
|
190
|
+
* public, and bucket names or object paths have no business in it. Null
|
|
191
|
+
* when the listing itself failed.
|
|
192
|
+
*/
|
|
193
|
+
exportCount: number | null;
|
|
194
|
+
/** Age of the newest completed export, in days. Null when there is none. */
|
|
195
|
+
newestExportAgeDays: number | null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Weekly cadence (Mondays, `scheduled-crons.yml`) + one day of slack — the
|
|
199
|
+
* same budget as `MAX_BACKUP_AGE_DAYS`, for the same reason: a schedule that
|
|
200
|
+
* stops producing is as broken as one that fails.
|
|
201
|
+
*/
|
|
202
|
+
export declare const MAX_EXPORT_AGE_DAYS = 8;
|
|
203
|
+
export declare function exportsHealth(markers: ExportMarker[] | null, ms: number, now?: number): ExportsCheck;
|
|
204
|
+
/**
|
|
205
|
+
* Org-creation volume, reduced to a health verdict (AGL-1536).
|
|
206
|
+
*
|
|
207
|
+
* The detection layer over the AGL-1534 rate limit: the limiter bounds each
|
|
208
|
+
* uid (3/h) and each IP (10/h), so a distributed farm holding every actor
|
|
209
|
+
* under both caps is invisible to it — but visible in aggregate. This turns
|
|
210
|
+
* "how many orgs appeared in the trailing hour" into the same 200/503
|
|
211
|
+
* contract the other health checks speak, so the existing uptime check +
|
|
212
|
+
* alert + email path is the listener, and a wave becomes an email instead of
|
|
213
|
+
* a surprise. The manual response lever is the AGL-1510 signups feature-lock.
|
|
214
|
+
*
|
|
215
|
+
* Pure on purpose, like `backupsHealth`: the route counts, this decides, the
|
|
216
|
+
* spec exercises every branch without a network.
|
|
217
|
+
*/
|
|
218
|
+
export interface SignupsCheck extends HealthCheck {
|
|
219
|
+
/**
|
|
220
|
+
* Orgs created inside the trailing window. A COUNT only — the endpoint is
|
|
221
|
+
* public, and org names, slugs or owners have no business in it. Null when
|
|
222
|
+
* the count query itself failed.
|
|
223
|
+
*/
|
|
224
|
+
recentOrgCreations: number | null;
|
|
225
|
+
/** The trailing window the count covers, so the body is self-describing. */
|
|
226
|
+
windowMinutes: number;
|
|
227
|
+
/** The count above which this reports degraded. */
|
|
228
|
+
threshold: number;
|
|
229
|
+
}
|
|
230
|
+
/** Trailing window the org-creation count covers. */
|
|
231
|
+
export declare const ORG_CREATION_WINDOW_MINUTES = 60;
|
|
232
|
+
/**
|
|
233
|
+
* Degraded above 10 creations/hour. Calibrated against two facts: production
|
|
234
|
+
* holds 4 orgs TOTAL (2026-08), so the organic baseline is ~zero per hour;
|
|
235
|
+
* and the AGL-1534 per-IP cap is 10/h, so a single maxed-out address can
|
|
236
|
+
* never trip this — 11+/h necessarily means multiple addresses, which is
|
|
237
|
+
* exactly the distributed signature the rate limiter cannot see. If a launch
|
|
238
|
+
* day legitimately beats this, the alert asks a human to glance at the orgs
|
|
239
|
+
* list and enjoy the view; that is the correct outcome, not a false positive.
|
|
240
|
+
*/
|
|
241
|
+
export declare const MAX_ORG_CREATIONS_PER_WINDOW = 10;
|
|
242
|
+
export declare function signupsHealth(recentOrgCreations: number | null, ms: number, threshold?: number): SignupsCheck;
|
|
243
|
+
/**
|
|
244
|
+
* Refused org creations, reduced to a health verdict (AGL-1907).
|
|
245
|
+
*
|
|
246
|
+
* `signupsHealth` above counts the signups that SUCCEEDED. That is the right
|
|
247
|
+
* alarm for a wave that gets through, and the wrong one for a wave that is
|
|
248
|
+
* being contained: when the AGL-1534 limiter starts refusing, org-creation
|
|
249
|
+
* volume goes *down*, so the check that watches volume reads calmer at exactly
|
|
250
|
+
* the moment scripted pressure arrives. The 429s are the signature. Before the
|
|
251
|
+
* doors open on Sep 1 this is also the only thing that can answer "has the
|
|
252
|
+
* limiter ever actually fired in production" — a control nobody has seen
|
|
253
|
+
* refuse is a control nobody has tested.
|
|
254
|
+
*
|
|
255
|
+
* Counted from `rateLimits/signupRefused_{minuteBucket}` markers, which carry
|
|
256
|
+
* per-reason counts and no identifiers. This is a separate check rather than
|
|
257
|
+
* more fields on `SignupsCheck` so the AGL-1536 verdict keeps its meaning
|
|
258
|
+
* exactly: a creation wave and a refusal wave are different events with
|
|
259
|
+
* different responses (feature-lock the signups door vs. leave the limiter to
|
|
260
|
+
* do its job and go read the refusal reasons).
|
|
261
|
+
*
|
|
262
|
+
* Pure on purpose, like its siblings: the route queries, this decides.
|
|
263
|
+
*/
|
|
264
|
+
export interface SignupRefusalMarker {
|
|
265
|
+
/** Refused attempts merged into this minute bucket. */
|
|
266
|
+
refusals?: number;
|
|
267
|
+
/**
|
|
268
|
+
* Split by what refused: `{ uid?: n, ip?: n, locked?: n, held?: n }`.
|
|
269
|
+
*
|
|
270
|
+
* `uid` and `ip` are the AGL-1534 rate limiter's two caps. `locked` and
|
|
271
|
+
* `held` come from the `beforeUserCreated` blocking function (AGL-2583) — a
|
|
272
|
+
* door the limiter never sees, because it decides account creation on the
|
|
273
|
+
* Identity Platform path before any Aglyn route runs.
|
|
274
|
+
*/
|
|
275
|
+
byReason?: Record<string, number>;
|
|
276
|
+
/**
|
|
277
|
+
* Account creations the blocking function admitted BLIND: the signups lock
|
|
278
|
+
* could not be read and nothing this instance remembered said it was pulled
|
|
279
|
+
* (AGL-2583).
|
|
280
|
+
*
|
|
281
|
+
* Deliberately its own field rather than another `byReason` entry. Nobody
|
|
282
|
+
* was refused, so folding it into the refusal split would make both counts
|
|
283
|
+
* lie — and it is graded on its own terms below.
|
|
284
|
+
*/
|
|
285
|
+
unreadable?: number;
|
|
286
|
+
/** When the most recent refusal in this bucket happened. */
|
|
287
|
+
refusedAtMs?: number;
|
|
288
|
+
}
|
|
289
|
+
export interface SignupRefusalsCheck extends HealthCheck {
|
|
290
|
+
/**
|
|
291
|
+
* Refused org creations inside the trailing window. A COUNT only — the
|
|
292
|
+
* endpoint is public, and the limiter's keys are a uid and a hashed client
|
|
293
|
+
* IP. Null when the query itself failed.
|
|
294
|
+
*/
|
|
295
|
+
refusedSignups: number | null;
|
|
296
|
+
/**
|
|
297
|
+
* Which cap did the refusing. A run that is almost all `ip` is one address
|
|
298
|
+
* hammering; a run that is almost all `uid` is many accounts each pushing
|
|
299
|
+
* past three, which is the distributed shape. Null when the query failed.
|
|
300
|
+
*/
|
|
301
|
+
refusedByReason: Record<string, number> | null;
|
|
302
|
+
/**
|
|
303
|
+
* Account creations decided WITHOUT the signups lock being readable
|
|
304
|
+
* (AGL-2583). Graded on its own, at zero tolerance, because it is
|
|
305
|
+
* categorically different from every entry in `refusedByReason`: those are
|
|
306
|
+
* controls working as designed, this is the platform answering the signups
|
|
307
|
+
* question without being able to consult it, and one of those is one too
|
|
308
|
+
* many. Null when the query itself failed.
|
|
309
|
+
*/
|
|
310
|
+
lockUnreadable: number | null;
|
|
311
|
+
/** Minutes since the most recent refusal, so the body dates itself. */
|
|
312
|
+
minutesSinceLast: number | null;
|
|
313
|
+
/** The trailing window the count covers, so the body is self-describing. */
|
|
314
|
+
windowMinutes: number;
|
|
315
|
+
/** The count above which this reports degraded. */
|
|
316
|
+
threshold: number;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Trailing window the refusal count covers. One hour, matching the limiter's
|
|
320
|
+
* own window (`org-create` is 3/h and 10/h) so the number is readable against
|
|
321
|
+
* the caps that produced it.
|
|
322
|
+
*/
|
|
323
|
+
export declare const SIGNUP_REFUSAL_WINDOW_MINUTES = 60;
|
|
324
|
+
/**
|
|
325
|
+
* Degraded above 50 refusals/hour.
|
|
326
|
+
*
|
|
327
|
+
* Not zero, unlike the limiter-degradation alarm: a refusal is the system
|
|
328
|
+
* working, and a real person who fumbles a workspace slug four times in an
|
|
329
|
+
* hour produces one. The number that means something is *sustained* refusal.
|
|
330
|
+
* Calibrated the same way AGL-1536's was: production holds single-digit orgs
|
|
331
|
+
* (2026-08), so organic refusals are ~zero per hour, and 50 is far above any
|
|
332
|
+
* plausible human while being reached in under a minute by a script. One
|
|
333
|
+
* determined address can generate this alone — that is intended, because "one
|
|
334
|
+
* address is hammering the signup door" is worth an email even though the
|
|
335
|
+
* limiter is containing it.
|
|
336
|
+
*/
|
|
337
|
+
export declare const MAX_SIGNUP_REFUSALS_PER_WINDOW = 50;
|
|
338
|
+
/**
|
|
339
|
+
* The marker field the blocking function counts blind decisions under
|
|
340
|
+
* (AGL-2583).
|
|
341
|
+
*
|
|
342
|
+
* `signupsCreationVerdict` answers `locked` when a completed read found the
|
|
343
|
+
* lever pulled, `held` when a failed read fell back on an earlier one that
|
|
344
|
+
* had, and — the case this names — admits the account when the read failed
|
|
345
|
+
* and nothing remembered said the lever was pulled. That last one refuses
|
|
346
|
+
* nobody, so it is not a `byReason` entry; it is the platform deciding the
|
|
347
|
+
* signups question without being able to consult it, which is the visible
|
|
348
|
+
* edge of a Firestore read outage on the account-creation path. AGL-2581 was
|
|
349
|
+
* three days of that same read failing, and its only trace was a
|
|
350
|
+
* `logger.warn` nothing alerts on.
|
|
351
|
+
*/
|
|
352
|
+
export declare const SIGNUP_LOCK_UNREADABLE_FIELD = "unreadable";
|
|
353
|
+
/**
|
|
354
|
+
* Blind signups-lock decisions tolerated per window: NONE.
|
|
355
|
+
*
|
|
356
|
+
* Deliberately not calibrated like `MAX_SIGNUP_REFUSALS_PER_WINDOW` above,
|
|
357
|
+
* which is a volume threshold over refusals that mean the system is working.
|
|
358
|
+
* A blind decision is never routine — a single one says the signups lock was
|
|
359
|
+
* unreadable at least once, and the lock is unenforceable for as long as that
|
|
360
|
+
* lasts. False positives here cost a glance at one endpoint.
|
|
361
|
+
*/
|
|
362
|
+
export declare const MAX_UNREADABLE_SIGNUP_DECISIONS_PER_WINDOW = 0;
|
|
363
|
+
export declare function signupRefusalsHealth(markers: SignupRefusalMarker[] | null, ms: number, now?: number, threshold?: number, windowMinutes?: number, unreadableThreshold?: number): SignupRefusalsCheck;
|
|
364
|
+
/**
|
|
365
|
+
* NOBODY GOT AN ACCOUNT WHILE PEOPLE WERE TRYING (AGL-2583).
|
|
366
|
+
*
|
|
367
|
+
* `signupsHealth` watches for a FLOOD. Its healthiest possible reading —
|
|
368
|
+
* zero orgs created in the trailing hour — is also what total failure looks
|
|
369
|
+
* like, so the monitor named "signups" reports green precisely when nobody
|
|
370
|
+
* can sign up. That is not a hypothetical: account creation was refused for
|
|
371
|
+
* every visitor from Sep 1 to Sep 4 (AGL-2581) and the signup check was
|
|
372
|
+
* green through all of it, because zero is comfortably under ten.
|
|
373
|
+
*
|
|
374
|
+
* ## Zero is only a signal next to a denominator
|
|
375
|
+
*
|
|
376
|
+
* Alone, "no accounts this hour" describes a quiet Tuesday night as loudly as
|
|
377
|
+
* an outage, and an alarm that fires on quiet nights is an alarm everyone
|
|
378
|
+
* mutes. What makes zero mean something is TRAFFIC: people arrived at the
|
|
379
|
+
* signup page and none of them ended up with an account.
|
|
380
|
+
*
|
|
381
|
+
* ## The denominator is ATTEMPTS, and was once SERVES (AGL-2714)
|
|
382
|
+
*
|
|
383
|
+
* It began as signup-page serves — `signupServed_` markers written when the
|
|
384
|
+
* page asks `/api/lockdown-status?feature=signups`, the one server request
|
|
385
|
+
* every rendering makes. That counts LOOKERS: crawlers, link previews, a
|
|
386
|
+
* person reading the plan names and closing the tab. On 2026-09-09 it paged
|
|
387
|
+
* "Signups is down" on an hour with six such hits and no account, while the
|
|
388
|
+
* signup page rendered clean, the lockdown was off, all seven auth doors were
|
|
389
|
+
* green and not one signup had been refused. One of the six was the
|
|
390
|
+
* diagnostic load made while investigating the alert.
|
|
391
|
+
*
|
|
392
|
+
* At this platform's conversion rate a quiet hour and a broken door produce
|
|
393
|
+
* the same reading, so no threshold on serves could separate them. The
|
|
394
|
+
* quantity was wrong, not the number.
|
|
395
|
+
*
|
|
396
|
+
* `signupAttempted_` markers are the right one: written at the top of
|
|
397
|
+
* `/api/orgs/create` before any outcome is known, so an attempt means
|
|
398
|
+
* somebody authenticated, filled the form and asked for an org. Arrivals with
|
|
399
|
+
* no account is then a statement about the door rather than about demand.
|
|
400
|
+
*
|
|
401
|
+
* Writing the marker BEFORE the outcome is what makes this stronger than the
|
|
402
|
+
* refusal check beside it: `signupRefusals` counts what the route chose to
|
|
403
|
+
* turn away, so a request that 500s, hangs, or dies between the limiter and
|
|
404
|
+
* the write leaves no refusal at all — and leaves an attempt with no org,
|
|
405
|
+
* which is exactly this verdict.
|
|
406
|
+
*
|
|
407
|
+
* Not GA4, for the reason it was never GA4: its API needs a service account
|
|
408
|
+
* and an OAuth round trip from a public health route, its data lands with
|
|
409
|
+
* hours of latency, and a monitor that depends on an analytics vendor is a
|
|
410
|
+
* monitor that reports an outage every time that vendor has one.
|
|
411
|
+
*
|
|
412
|
+
* The denominator UNDER-counts on purpose and that is the safe direction: a
|
|
413
|
+
* marker write is fire-and-forget and a lost one is simply not counted.
|
|
414
|
+
* Under-counting can only make this check quieter, never louder — it cannot
|
|
415
|
+
* invent attempts that did not happen, so it cannot invent an outage.
|
|
416
|
+
*
|
|
417
|
+
* Serves are still read and still reported, as CONTEXT. They cost nothing
|
|
418
|
+
* extra and they answer the second question an on-call person asks: was
|
|
419
|
+
* anybody even looking.
|
|
420
|
+
*
|
|
421
|
+
* Pure on purpose, like its siblings: the route counts, this decides.
|
|
422
|
+
*/
|
|
423
|
+
export interface SignupServedMarker {
|
|
424
|
+
/** Signup-page serves merged into this minute bucket. */
|
|
425
|
+
serves?: number;
|
|
426
|
+
/** When the most recent serve in this bucket happened. */
|
|
427
|
+
servedAtMs?: number;
|
|
428
|
+
}
|
|
429
|
+
export interface SignupAttemptMarker {
|
|
430
|
+
/** Org-creation attempts merged into this minute bucket. */
|
|
431
|
+
attempts?: number;
|
|
432
|
+
/** When the most recent attempt in this bucket happened. */
|
|
433
|
+
attemptedAtMs?: number;
|
|
434
|
+
}
|
|
435
|
+
export interface SignupDroughtCheck extends HealthCheck {
|
|
436
|
+
/**
|
|
437
|
+
* Signup pages served inside the trailing window — the denominator. A
|
|
438
|
+
* COUNT only; the markers carry no visitor, no IP and no referrer. Null
|
|
439
|
+
* when the query itself failed.
|
|
440
|
+
*/
|
|
441
|
+
signupPagesServed: number | null;
|
|
442
|
+
/**
|
|
443
|
+
* Org-creation attempts inside the same window — the denominator the
|
|
444
|
+
* verdict is actually taken on (AGL-2714). Null when that query failed.
|
|
445
|
+
*/
|
|
446
|
+
signupAttempts: number | null;
|
|
447
|
+
/** Accounts (orgs) actually created in the same window. The numerator. */
|
|
448
|
+
orgCreations: number | null;
|
|
449
|
+
/** The trailing window all three numbers cover, so the body is self-describing. */
|
|
450
|
+
windowMinutes: number;
|
|
451
|
+
/**
|
|
452
|
+
* Attempts below which zero creations is treated as a quiet hour rather
|
|
453
|
+
* than an outage. Named `minimumTraffic` still, because it is the same
|
|
454
|
+
* question — how much has to have happened before zero means something —
|
|
455
|
+
* and renaming it would break every monitor body that reads it.
|
|
456
|
+
*/
|
|
457
|
+
minimumTraffic: number;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Trailing window the drought verdict covers. One hour, matching the sibling
|
|
461
|
+
* checks so the three numbers on this endpoint are all read against the same
|
|
462
|
+
* clock — and long enough that a single slow afternoon does not page anyone.
|
|
463
|
+
*/
|
|
464
|
+
export declare const SIGNUP_DROUGHT_WINDOW_MINUTES = 60;
|
|
465
|
+
/**
|
|
466
|
+
* Attempts in the window below which zero accounts means nothing.
|
|
467
|
+
*
|
|
468
|
+
* Three, against attempts rather than the five it once meant against serves
|
|
469
|
+
* (AGL-2714). The unit changed, so the number had to be re-argued rather than
|
|
470
|
+
* carried over.
|
|
471
|
+
*
|
|
472
|
+
* Not one: a browser can lose a request, and a person can start the form and
|
|
473
|
+
* abandon it between authenticating and the org write. Not ten either — at a
|
|
474
|
+
* handful of signups a week, waiting for ten attempts to fail would mean
|
|
475
|
+
* waiting days to notice a door that stopped opening, which is the failure
|
|
476
|
+
* this check exists to catch inside an hour.
|
|
477
|
+
*
|
|
478
|
+
* Three distinct requests to create an org, none of which produced one and
|
|
479
|
+
* none of which was refused, is a door that does not open. That reading has
|
|
480
|
+
* no quiet-hour interpretation: nobody attempts three times by browsing.
|
|
481
|
+
*/
|
|
482
|
+
export declare const MIN_SIGNUP_TRAFFIC_FOR_DROUGHT = 3;
|
|
483
|
+
export declare function signupDroughtHealth(markers: SignupServedMarker[] | null, attemptMarkers: SignupAttemptMarker[] | null, orgCreations: number | null, ms: number, minimumTraffic?: number, windowMinutes?: number): SignupDroughtCheck;
|
|
484
|
+
/**
|
|
485
|
+
* Rate-limiter degradation, reduced to a health verdict (AGL-1693).
|
|
486
|
+
*
|
|
487
|
+
* `consumeRateLimit` fails SOFT: when the Firestore counter is unreachable it
|
|
488
|
+
* answers from a per-instance `Map` and flags `degraded`. AGL-1679 made that
|
|
489
|
+
* findable after the fact — on recovery the instance writes one summary
|
|
490
|
+
* document `rateLimits/degraded_{minuteBucket}` — but nothing reads it, so a
|
|
491
|
+
* degraded window during the beta still passes unnoticed in real time. This
|
|
492
|
+
* turns "did any limiter fall back recently" into the same 200/503 contract
|
|
493
|
+
* the sibling checks speak, so the AGL-1502 uptime check + alert + email path
|
|
494
|
+
* is the listener.
|
|
495
|
+
*
|
|
496
|
+
* **The window is the whole design.** Markers carry a 30-day `expiresAt`, so
|
|
497
|
+
* "does a marker exist" would leave the endpoint red for a month after a
|
|
498
|
+
* thirty-second blip — a recovered incident permanently paging is how a check
|
|
499
|
+
* gets muted, and muted is worse than absent. That is the opposite of the
|
|
500
|
+
* backups check, which stays red BY DESIGN because a missing restore point is
|
|
501
|
+
* a condition that persists until someone fixes it (DISASTER_RECOVERY.md gap
|
|
502
|
+
* 2). A degraded limiter window is an EVENT: it is over, and what is owed is
|
|
503
|
+
* a notification, not a standing alarm. So the verdict covers a trailing
|
|
504
|
+
* window and clears itself.
|
|
505
|
+
*
|
|
506
|
+
* Filtering on `lastAtMs` rather than the marker's id: the id is bucketed on
|
|
507
|
+
* `firstAtMs`, so a long episode's document is stamped with the minute the
|
|
508
|
+
* outage STARTED. An id-range window would miss exactly the longest episodes,
|
|
509
|
+
* which are the ones worth waking up for.
|
|
510
|
+
*
|
|
511
|
+
* Pure on purpose, like `backupsHealth` and `signupsHealth`: the route
|
|
512
|
+
* queries, this decides, the spec exercises every branch without a network.
|
|
513
|
+
*/
|
|
514
|
+
export interface RateLimitDegradationMarker {
|
|
515
|
+
/** Calls that fell back to the per-instance cap. */
|
|
516
|
+
calls?: number;
|
|
517
|
+
/** Instance-episodes merged into this minute bucket. */
|
|
518
|
+
episodes?: number;
|
|
519
|
+
/** When the episode's last fallback happened. */
|
|
520
|
+
lastAtMs?: number;
|
|
521
|
+
/** The failure code the episode ended on. */
|
|
522
|
+
code?: string;
|
|
523
|
+
}
|
|
524
|
+
export interface RateLimitsCheck extends HealthCheck {
|
|
525
|
+
/**
|
|
526
|
+
* Fallback calls inside the trailing window. A COUNT only — the endpoint is
|
|
527
|
+
* public, and limiter keys are hashed client IPs. Null when the query
|
|
528
|
+
* itself failed.
|
|
529
|
+
*/
|
|
530
|
+
degradedCalls: number | null;
|
|
531
|
+
/** Instance-episodes inside the window. 2+ means several instances saw it. */
|
|
532
|
+
degradedEpisodes: number | null;
|
|
533
|
+
/** Minutes since the most recent fallback, so the body dates itself. */
|
|
534
|
+
minutesSinceLast: number | null;
|
|
535
|
+
/** The trailing window the counts cover, so the body is self-describing. */
|
|
536
|
+
windowMinutes: number;
|
|
537
|
+
/** The count above which this reports degraded. */
|
|
538
|
+
threshold: number;
|
|
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
|
+
*/
|
|
550
|
+
export declare const RATE_LIMIT_DEGRADED_WINDOW_MINUTES = 30;
|
|
551
|
+
/**
|
|
552
|
+
* Any fallback at all is worth an email. Unlike the signup alarm there is no
|
|
553
|
+
* organic baseline to clear: a healthy deployment produces exactly zero of
|
|
554
|
+
* these, and one is already the statement "for some requests, the global cap
|
|
555
|
+
* was not the cap". Firestore retries transactions internally, so a failure
|
|
556
|
+
* that reaches this code is not a single unlucky contention.
|
|
557
|
+
*/
|
|
558
|
+
export declare const MAX_DEGRADED_CALLS_PER_WINDOW = 0;
|
|
559
|
+
export declare function rateLimitsHealth(markers: RateLimitDegradationMarker[] | null, ms: number, now?: number, threshold?: number, windowMinutes?: number): RateLimitsCheck;
|
|
560
|
+
/**
|
|
561
|
+
* Uncaught server-side errors, reduced to a health verdict (AGL-1921).
|
|
562
|
+
*
|
|
563
|
+
* ## The gap this closes
|
|
564
|
+
*
|
|
565
|
+
* Of the eleven alert policies in `aglyn-main`, not one could see a server
|
|
566
|
+
* error rate. Every check is a LIVENESS probe on one URL: `/api/health` can be
|
|
567
|
+
* green while `/api/billing/checkout` 500s for every paying customer, which is
|
|
568
|
+
* the single most likely shape of a launch-day incident and the one nothing
|
|
569
|
+
* could page on. Server errors lived only in the Vercel runtime log, which
|
|
570
|
+
* retains ~60 minutes and drains nowhere (AGL-1799).
|
|
571
|
+
*
|
|
572
|
+
* The first pass gave both apps an `onRequestError` hook that forwards each
|
|
573
|
+
* error to a `server-errors` log in `aglyn-main`. That closed the CAPTURE gap
|
|
574
|
+
* and left the reading gap wide open, and the reading gap is the one this repo
|
|
575
|
+
* keeps losing to — `/api/health/crons` was correct about a broken job for
|
|
576
|
+
* fifty-one hours because nothing asked it. Measured 2026-08-24: the
|
|
577
|
+
* production credential is refused `entries:list` on that log
|
|
578
|
+
* (`403 Permission denied for all log views`), so no probe here can read what
|
|
579
|
+
* the hook writes. The only reader that log can have is a GCP alert policy
|
|
580
|
+
* created by hand.
|
|
581
|
+
*
|
|
582
|
+
* So the hook ALSO counts into a `rateLimits/serverError_{minute}` marker,
|
|
583
|
+
* and this grades those counts into the same 200/503 contract the sibling
|
|
584
|
+
* checks speak — which hands the signal to readers that already exist and
|
|
585
|
+
* already run: the 15-minute GitHub uptime probe, the external keyword
|
|
586
|
+
* monitors, and `docs.aglyn.com/status`.
|
|
587
|
+
*
|
|
588
|
+
* ## What it can and cannot see, exactly
|
|
589
|
+
*
|
|
590
|
+
* It counts what `onRequestError` counts: an uncaught throw in a render or a
|
|
591
|
+
* route handler, in the nodejs runtime. It does NOT count an error that kills
|
|
592
|
+
* the process before the hook runs, a platform-level 5xx that never reaches
|
|
593
|
+
* our code (function timeout, OOM, cold-start 502), or anything thrown in the
|
|
594
|
+
* edge runtime. A Vercel log drain sees all of those and remains the real fix.
|
|
595
|
+
* This is the arm that ships without a purchase, and its blind spots are
|
|
596
|
+
* written down rather than glossed — `docs/UPTIME_AND_SLA.md`.
|
|
597
|
+
*
|
|
598
|
+
* ## The third state is the one that matters here
|
|
599
|
+
*
|
|
600
|
+
* A monitoring path that cannot measure MUST NOT report a zero. `null` markers
|
|
601
|
+
* — the query failed — is `errors-unavailable` and degraded, never "0 errors".
|
|
602
|
+
* That is the same rule `rateLimitsHealth` and `billingWebhookHealth` follow,
|
|
603
|
+
* and on this check it is the difference between "nothing is wrong" and "we
|
|
604
|
+
* have no idea", which during a 5xx spike are the two readings furthest apart.
|
|
605
|
+
*
|
|
606
|
+
* ## It must be able to go GREEN
|
|
607
|
+
*
|
|
608
|
+
* A spike is an EVENT, not a condition (the AGL-1843 rule). Markers live seven
|
|
609
|
+
* days, so "does a marker exist" would hold this red for a week after a
|
|
610
|
+
* one-minute blip, and a check that stays red after recovery is a check that
|
|
611
|
+
* gets muted. The verdict covers a trailing window and clears itself.
|
|
612
|
+
*
|
|
613
|
+
* Pure on purpose, like its siblings: the route queries, this decides, the
|
|
614
|
+
* spec exercises every branch without a network.
|
|
615
|
+
*/
|
|
616
|
+
export interface ServerErrorMarker {
|
|
617
|
+
/** Uncaught server errors merged into this minute bucket. */
|
|
618
|
+
errors?: number;
|
|
619
|
+
/** Split by deployment — `{ 'console-web': n, 'tenant-web': n }`. */
|
|
620
|
+
byService?: Record<string, number>;
|
|
621
|
+
/** When the most recent error in this bucket happened. */
|
|
622
|
+
erroredAtMs?: number;
|
|
623
|
+
}
|
|
624
|
+
export interface ServerErrorsCheck extends HealthCheck {
|
|
625
|
+
/**
|
|
626
|
+
* Uncaught server errors inside the trailing window. A COUNT only — the
|
|
627
|
+
* endpoint is public, and messages, stacks and route patterns have no
|
|
628
|
+
* business in it (they are in the Logging entry, which is not public). Null
|
|
629
|
+
* when the query itself failed, and null is NEVER zero.
|
|
630
|
+
*/
|
|
631
|
+
serverErrors: number | null;
|
|
632
|
+
/**
|
|
633
|
+
* Which deployment produced them. The first thing an incident needs, and it
|
|
634
|
+
* is a deployment name rather than anything about a request. Null when the
|
|
635
|
+
* query failed.
|
|
636
|
+
*/
|
|
637
|
+
byService: Record<string, number> | null;
|
|
638
|
+
/** Minutes since the most recent error, so the body dates itself. */
|
|
639
|
+
minutesSinceLast: number | null;
|
|
640
|
+
/** The trailing window the count covers, so the body is self-describing. */
|
|
641
|
+
windowMinutes: number;
|
|
642
|
+
/** The count above which this reports degraded. */
|
|
643
|
+
threshold: number;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Trailing window the error count covers.
|
|
647
|
+
*
|
|
648
|
+
* Bounded from below by the alert path, not by taste — the same arithmetic as
|
|
649
|
+
* `RATE_LIMIT_DEGRADED_WINDOW_MINUTES`: the probe memoizes for 2 minutes, the
|
|
650
|
+
* slowest external check runs every 15, and an alert policy wants a minute of
|
|
651
|
+
* sustained failure, so a window under ~18 minutes can go red and green again
|
|
652
|
+
* before that check tells anyone. 30 leaves margin and still clears itself
|
|
653
|
+
* inside half an hour of the last error.
|
|
654
|
+
*/
|
|
655
|
+
export declare const SERVER_ERROR_WINDOW_MINUTES = 30;
|
|
656
|
+
/**
|
|
657
|
+
* Errors tolerated in the window.
|
|
658
|
+
*
|
|
659
|
+
* NOT zero, unlike the rate-limiter and billing alarms, and the difference is
|
|
660
|
+
* real rather than a matter of nerve. Those two watch controls that a healthy
|
|
661
|
+
* deployment exercises exactly never. This one watches a count that a single
|
|
662
|
+
* cold-start Firestore deadline, or one visitor's malformed request reaching a
|
|
663
|
+
* route that throws instead of 400ing, can make non-zero on a perfectly good
|
|
664
|
+
* day — and an alarm that pages on the first of those gets muted before the
|
|
665
|
+
* one that matters arrives.
|
|
666
|
+
*
|
|
667
|
+
* Five over thirty minutes is chosen against the only two facts available.
|
|
668
|
+
* Production serves single-digit orgs (2026-08), so the organic rate of
|
|
669
|
+
* UNCAUGHT server errors is ~zero — the codebase answers its expected failures
|
|
670
|
+
* with 4xx, and reaching this hook means nothing handled it. And the shape
|
|
671
|
+
* being caught is "a route is failing for real users", which does not produce
|
|
672
|
+
* six errors an hour; it produces six in a minute. There is deliberately no
|
|
673
|
+
* pretence of calibration: the beta window exists to produce a baseline, and
|
|
674
|
+
* `SERVER_ERROR_ALARM_MAX_ERRORS` moves this without a deploy when it does.
|
|
675
|
+
*/
|
|
676
|
+
export declare const MAX_SERVER_ERRORS_PER_WINDOW = 5;
|
|
677
|
+
export declare function serverErrorsHealth(markers: ServerErrorMarker[] | null, ms: number, now?: number, threshold?: number, windowMinutes?: number): ServerErrorsCheck;
|
|
678
|
+
/**
|
|
679
|
+
* How long a health door may reuse its verdict (AGL-2947).
|
|
680
|
+
*
|
|
681
|
+
* Cut from one budget: an outage must reach a person within ten minutes. A
|
|
682
|
+
* green verdict computed just before the onset keeps being served for this
|
|
683
|
+
* long, and only then can a prober see red. The fastest prober on most doors
|
|
684
|
+
* is UptimeRobot, every five minutes, which then confirms before it notifies.
|
|
685
|
+
* So the worst case is this + 5 + that confirmation: at two minutes the budget
|
|
686
|
+
* holds for any confirmation delay up to three. The five minutes every door
|
|
687
|
+
* used before spent the whole ten minutes before a notification could leave.
|
|
688
|
+
*
|
|
689
|
+
* It still bounds what a public door can be made to cost: at most one
|
|
690
|
+
* computation per instance per two minutes, and {@link memoizeWithTtl} shares
|
|
691
|
+
* a computation among the callers that arrive while it runs.
|
|
692
|
+
*/
|
|
693
|
+
export declare const HEALTH_PROBE_TTL_MS: number;
|
|
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
|
+
*/
|
|
713
|
+
export declare function memoizeWithTtl<T>(ttlMs: number, probe: () => Promise<T>, now?: () => number): () => Promise<T>;
|
|
714
|
+
/**
|
|
715
|
+
* The error beacon's own liveness, reduced to a health verdict (AGL-1923).
|
|
716
|
+
*
|
|
717
|
+
* ## The failure this exists for
|
|
718
|
+
*
|
|
719
|
+
* The `Client error beacon` alert policy is a log-match: it fires when an
|
|
720
|
+
* entry APPEARS in `client-errors`. A policy of that shape can only report
|
|
721
|
+
* presence, so if the thing writing the entries stops writing, the policy
|
|
722
|
+
* goes quiet — and quiet is the reading it also gives when everything is
|
|
723
|
+
* healthy. **A dead beacon is indistinguishable from zero errors**, and
|
|
724
|
+
* `reportClientErrors` has three fail-soft paths that each end in a
|
|
725
|
+
* `console.warn` to a log that retains an hour and drains nowhere
|
|
726
|
+
* (AGL-1799). Error Reporting would show zero errors; the policy would stay
|
|
727
|
+
* silent; both readings look exactly like a clean launch.
|
|
728
|
+
*
|
|
729
|
+
* ## Why this shape and not a metric-absence policy
|
|
730
|
+
*
|
|
731
|
+
* AGL-1923 proposed a log-based metric plus `conditionAbsent`. This is the
|
|
732
|
+
* same guarantee with two properties that one lacks. It is PROVABLE — the
|
|
733
|
+
* verdict is graded synchronously against the write's own status, so the
|
|
734
|
+
* degraded branch can be induced and observed rather than waited out over an
|
|
735
|
+
* absence window. And it needs nothing new to run it: the uptime probe is
|
|
736
|
+
* what winds the dead-man's switch, so there is no cron that can itself stop
|
|
737
|
+
* without anyone noticing (which would only move the problem one layer out).
|
|
738
|
+
*
|
|
739
|
+
* ## It must be able to go GREEN
|
|
740
|
+
*
|
|
741
|
+
* The condition is an EVENT, not a state. A failed write degrades; the next
|
|
742
|
+
* successful write clears it, within one probe TTL — nothing latches and
|
|
743
|
+
* there is no retention window to age out of. That is the AGL-1843 rule
|
|
744
|
+
* applied before the fact: the clearing event is named, and it is "the next
|
|
745
|
+
* heartbeat that reaches Cloud Logging". The durable marker AGL-2713 added is
|
|
746
|
+
* evidence of a LANDING, never of a failure, so it can only ever shorten the
|
|
747
|
+
* red — a stale one forgives nothing.
|
|
748
|
+
*
|
|
749
|
+
* Pure on purpose, like its siblings: the route writes, this decides, the
|
|
750
|
+
* spec exercises every branch without a network.
|
|
751
|
+
*/
|
|
752
|
+
export interface BeaconCheck extends HealthCheck {
|
|
753
|
+
/**
|
|
754
|
+
* The log id the heartbeat targets, so the body names what to query during
|
|
755
|
+
* an incident rather than making someone go and find it.
|
|
756
|
+
*/
|
|
757
|
+
logId: string;
|
|
758
|
+
/** Which deployment's credential was exercised — console and tenant differ. */
|
|
759
|
+
service: string;
|
|
760
|
+
/**
|
|
761
|
+
* How many heartbeat writes this probe attempted. Two means the first one
|
|
762
|
+
* missed, which is a fact a green row must still carry: a credential that
|
|
763
|
+
* needs a second attempt every time is on its way to failing outright.
|
|
764
|
+
*/
|
|
765
|
+
attempts: number;
|
|
766
|
+
/**
|
|
767
|
+
* Minutes since a heartbeat last durably landed, or null when the answer
|
|
768
|
+
* could not be established — which includes "none ever has". Null is what
|
|
769
|
+
* makes a missed heartbeat unforgivable, so the two cases fold safely.
|
|
770
|
+
*/
|
|
771
|
+
minutesSinceHeartbeat: number | null;
|
|
772
|
+
/** How long a missed heartbeat is tolerated before this door reds. */
|
|
773
|
+
graceMinutes: number;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Codes that mean the heartbeat got no ANSWER, as opposed to a refusal
|
|
777
|
+
* (AGL-2713).
|
|
778
|
+
*
|
|
779
|
+
* The distinction is the whole of the tolerance below, so it is named once
|
|
780
|
+
* and asserted directly rather than being spelled inline at the branch.
|
|
781
|
+
*
|
|
782
|
+
* - `credential-unavailable` — the admin app is configured and its credential
|
|
783
|
+
* simply did not mint a token on this attempt. On a cold lambda that is the
|
|
784
|
+
* first outbound TLS of the instance's life, and it is the measured shape of
|
|
785
|
+
* both 2026-09-09 blips.
|
|
786
|
+
* - `transport-*` — the Logging write never came back inside its 4s deadline,
|
|
787
|
+
* or the fetch layer failed. Same class: nothing was decided.
|
|
788
|
+
*
|
|
789
|
+
* Everything else is a DECISION and must never be tolerated. `http-401` and
|
|
790
|
+
* `http-403` are a lost `logging.logEntries.create` grant, `http-429` is
|
|
791
|
+
* quota, and `no-credential` is a deployment whose `FIREBASE_*` env is absent
|
|
792
|
+
* — every one of them permanent until a person fixes it, and every one of
|
|
793
|
+
* them a state in which browser errors are being collected by nothing. So is
|
|
794
|
+
* `heartbeat-unavailable`, which means a writer documented never to throw
|
|
795
|
+
* threw.
|
|
796
|
+
*/
|
|
797
|
+
export declare function isTransientBeaconCode(code: string | undefined): boolean;
|
|
798
|
+
/**
|
|
799
|
+
* How a missed heartbeat is graded (AGL-2713).
|
|
800
|
+
*
|
|
801
|
+
* `landedAtMs` is the durable, cross-instance fact — the last time ANY
|
|
802
|
+
* instance of this deployment put an entry in the log. It has to be durable:
|
|
803
|
+
* the probe memo dies with the lambda, so an in-process counter is at one on
|
|
804
|
+
* every cold start and would forgive nothing at all on the one shape this
|
|
805
|
+
* exists for.
|
|
806
|
+
*/
|
|
807
|
+
export interface BeaconTolerance {
|
|
808
|
+
/** Heartbeat writes this probe made. Defaults to the single-attempt shape. */
|
|
809
|
+
attempts?: number;
|
|
810
|
+
/** When a heartbeat last durably landed; null when that cannot be shown. */
|
|
811
|
+
landedAtMs?: number | null;
|
|
812
|
+
/** How long a miss is forgiven after the last landing. Zero forgives none. */
|
|
813
|
+
graceMs?: number;
|
|
814
|
+
now?: number;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* ## Tolerating ONE miss without becoming a door that cannot red (AGL-2713)
|
|
818
|
+
*
|
|
819
|
+
* `/api/health/error-beacon` on the console answered `503 no-credential` twice
|
|
820
|
+
* on 2026-09-09 — 15:12 and 17:22 UTC, each shortly after new capacity from
|
|
821
|
+
* the 15:08 deploy, each self-clearing within minutes, with the beacon code
|
|
822
|
+
* path changed by one comment line since the previous release. Between them
|
|
823
|
+
* they took the 15-minute uptime workflow red and opened four GCP alert
|
|
824
|
+
* events, on a monitor whose entire value is that somebody believes it.
|
|
825
|
+
*
|
|
826
|
+
* The tolerance is deliberately NOT "forgive a failure". It is three
|
|
827
|
+
* conditions that must all hold, and each one is what keeps the door able to
|
|
828
|
+
* go red:
|
|
829
|
+
*
|
|
830
|
+
* 1. the code says nothing was decided ({@link isTransientBeaconCode});
|
|
831
|
+
* 2. a heartbeat is PROVEN to have landed within `graceMs` — proof being a
|
|
832
|
+
* durable marker a successful write left behind, so a deployment whose
|
|
833
|
+
* credential never worked has nothing to forgive with and reds on its
|
|
834
|
+
* first probe;
|
|
835
|
+
* 3. that proof is not from the future, because a clock that ran backwards
|
|
836
|
+
* must not mint an unbounded grace.
|
|
837
|
+
*
|
|
838
|
+
* Absence of proof reds. A marker store that cannot be read reds. That is the
|
|
839
|
+
* same rule the `*-unavailable` arms of every sibling grader apply, pointed at
|
|
840
|
+
* the evidence for calm rather than at the evidence for alarm.
|
|
841
|
+
*/
|
|
842
|
+
export declare function beaconHealth(write: {
|
|
843
|
+
ok: boolean;
|
|
844
|
+
code?: string;
|
|
845
|
+
} | null, logId: string, service: string, ms: number, tolerance?: BeaconTolerance): BeaconCheck;
|
|
846
|
+
/**
|
|
847
|
+
* A tenant page render, reduced to a health verdict (AGL-2486).
|
|
848
|
+
*
|
|
849
|
+
* ## The gap this closes
|
|
850
|
+
*
|
|
851
|
+
* `marketing-home` (`aglyn.com/`) and `customer-site` (`demo.aglyn.app/`) are
|
|
852
|
+
* the only two external checks that watched a REAL PAGE rather than a health
|
|
853
|
+
* endpoint. Both sat at 0% from 2026-08-21, because bot protection answered
|
|
854
|
+
* Google's checkers with a 429 checkpoint. The health-path firewall bypass
|
|
855
|
+
* that recovered `tenant-health` and `beacon-heartbeat tenant` cannot reach
|
|
856
|
+
* them: they fetch `/`, not `/api/health/*`.
|
|
857
|
+
*
|
|
858
|
+
* The alternative was allowlisting Google's 54 checker IPs — rejected because
|
|
859
|
+
* an IP-valued bypass rule is one the drift checker cannot meaningfully
|
|
860
|
+
* assert, and Google rotates the list. So the render moves to where the
|
|
861
|
+
* bypass already is: an endpoint under `/api/health` that renders the page
|
|
862
|
+
* server-side and grades the result.
|
|
863
|
+
*
|
|
864
|
+
* ## Why this is not "200 because the route exists"
|
|
865
|
+
*
|
|
866
|
+
* The caller runs the SAME loader the catch-all page route runs, against the
|
|
867
|
+
* SAME host, and this grades what came back. A page that resolves no host,
|
|
868
|
+
* 404s, redirects away, or composes an EMPTY node tree is a failure — and
|
|
869
|
+
* those are the shapes an outage actually takes. A canary that cannot go red
|
|
870
|
+
* for a broken page is worse than the dark check it replaces, because it
|
|
871
|
+
* reports a confidence it has not earned.
|
|
872
|
+
*
|
|
873
|
+
* ## What is deliberately NOT asserted
|
|
874
|
+
*
|
|
875
|
+
* Never a string from the page. The marker is STRUCTURAL — a host resolved
|
|
876
|
+
* and a non-empty composed node tree. Asserting copy would page whoever is
|
|
877
|
+
* on call for an ordinary content edit, and this endpoint is public, so
|
|
878
|
+
* customer content must not appear in its body either. `nodeCount` is a
|
|
879
|
+
* count, which is enough to tell "rendered nothing" from "rendered" without
|
|
880
|
+
* disclosing what was rendered.
|
|
881
|
+
*/
|
|
882
|
+
export type RenderOutcome =
|
|
883
|
+
/** The loader returned props. `nodeCount` is the composed node total. */
|
|
884
|
+
{
|
|
885
|
+
kind: 'rendered';
|
|
886
|
+
hostResolved: boolean;
|
|
887
|
+
nodeCount: number;
|
|
888
|
+
}
|
|
889
|
+
/** The loader chose the not-found branch. */
|
|
890
|
+
| {
|
|
891
|
+
kind: 'not-found';
|
|
892
|
+
}
|
|
893
|
+
/** The loader chose to redirect — a home page should never do this. */
|
|
894
|
+
| {
|
|
895
|
+
kind: 'redirect';
|
|
896
|
+
}
|
|
897
|
+
/** The loader threw, or could not be reached at all. */
|
|
898
|
+
| {
|
|
899
|
+
kind: 'unavailable';
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* No target host is configured. A failure on purpose: "we are watching
|
|
903
|
+
* nothing" must never read the same as "the page is fine".
|
|
904
|
+
*/
|
|
905
|
+
| {
|
|
906
|
+
kind: 'not-configured';
|
|
907
|
+
};
|
|
908
|
+
export interface RenderCheck extends HealthCheck {
|
|
909
|
+
/**
|
|
910
|
+
* Composed nodes the page produced. A COUNT, never the nodes: this endpoint
|
|
911
|
+
* is public and the nodes are the customer's page. Zero is the failure.
|
|
912
|
+
*/
|
|
913
|
+
nodeCount: number;
|
|
914
|
+
/**
|
|
915
|
+
* Which tenant host was rendered — a CONFIGURED host, never one derived
|
|
916
|
+
* from the request, so no caller can choose what we render. Empty string
|
|
917
|
+
* when nothing is configured.
|
|
918
|
+
*/
|
|
919
|
+
host: string;
|
|
920
|
+
}
|
|
921
|
+
export declare function renderHealth(outcome: RenderOutcome, host: string, ms: number): RenderCheck;
|
|
922
|
+
/**
|
|
923
|
+
* Billing webhook delivery, reduced to a health verdict (AGL-1924).
|
|
924
|
+
*
|
|
925
|
+
* ## The gap this closes
|
|
926
|
+
*
|
|
927
|
+
* Of the alert policies in `aglyn-main`, none watched billing. Sept 1 opens to
|
|
928
|
+
* PAYING customers, and the billing path is the one place where a failure
|
|
929
|
+
* nobody notices converts directly into lost revenue and a support incident
|
|
930
|
+
* per affected customer. The webhook has a documented history of failing
|
|
931
|
+
* exactly that way: AGL-1551 (every delivery rejected `400 Invalid signature`
|
|
932
|
+
* for a week, behind a green "Active" badge), AGL-1560 (a secret roll 400ing
|
|
933
|
+
* roughly half of deliveries), AGL-1552 (the replay window making every retry
|
|
934
|
+
* unreceivable), AGL-1798 (`charge.refunded` never subscribed). Each is a
|
|
935
|
+
* total or near-total failure of the billing event path, and not one of them
|
|
936
|
+
* would have paged anyone.
|
|
937
|
+
*
|
|
938
|
+
* ## Why the denominator has to come from Stripe
|
|
939
|
+
*
|
|
940
|
+
* `stripeEvents` alone cannot answer this. `route.ts` returns 400 BEFORE
|
|
941
|
+
* claiming the idempotency document, so a rejected delivery writes nothing —
|
|
942
|
+
* an empty collection reads identically whether nothing happened or
|
|
943
|
+
* everything was refused. That is precisely how the 2026-08-14 checklist tick
|
|
944
|
+
* came out green while AGL-1551 was live. Stripe supplies what it TRIED to
|
|
945
|
+
* deliver; we supply what we processed; the gap is the failure.
|
|
946
|
+
*
|
|
947
|
+
* ## Why this cannot false-page on a quiet night
|
|
948
|
+
*
|
|
949
|
+
* The verdict never keys on the ABSENCE of events. At beta volume a night
|
|
950
|
+
* with zero deliveries is legitimate, and a naive freshness rule would page
|
|
951
|
+
* on it — the AGL-1843 mistake in a new costume. `undelivered` is a count of
|
|
952
|
+
* deliveries Stripe attempted AND failed, so a quiet window scores zero and
|
|
953
|
+
* reads healthy for the right reason. `emitted` and `processed` are carried
|
|
954
|
+
* for the human reading the incident and are DELIBERATELY not inputs to the
|
|
955
|
+
* verdict: no floor for them exists yet, and inventing one before the beta
|
|
956
|
+
* produces a baseline would be a threshold nobody could defend.
|
|
957
|
+
*
|
|
958
|
+
* ## Division of labour with the AGL-1906 audit
|
|
959
|
+
*
|
|
960
|
+
* `tools/scripts/lib/stripe-webhook-health.mjs` asserts the endpoint's full
|
|
961
|
+
* subscribed-event list against `WEBHOOK_EVENTS`. That list is deliberately
|
|
962
|
+
* NOT duplicated here — a second copy is the exact failure its own comment
|
|
963
|
+
* warns about, and a subscription set is config that changes on deploys, not
|
|
964
|
+
* a thing that changes minute to minute. This watches the two facts that DO
|
|
965
|
+
* move continuously: whether the destination is still enabled, and whether
|
|
966
|
+
* deliveries are landing.
|
|
967
|
+
*
|
|
968
|
+
* ## It must be able to go GREEN
|
|
969
|
+
*
|
|
970
|
+
* Everything here is a trailing window over live Stripe state. A destination
|
|
971
|
+
* re-enabled, or a fixed handler whose retries then succeed, reads healthy on
|
|
972
|
+
* the next probe. There is no marker to age out and nothing to latch
|
|
973
|
+
* (AGL-1843): the clearing event is "a window with no failed deliveries and
|
|
974
|
+
* an enabled endpoint".
|
|
975
|
+
*
|
|
976
|
+
* Pure on purpose, like its siblings: the route fetches, this decides, the
|
|
977
|
+
* spec exercises every branch without a network.
|
|
978
|
+
*/
|
|
979
|
+
export interface BillingWebhookFacts {
|
|
980
|
+
/** Our production destination, as Stripe currently holds it. */
|
|
981
|
+
endpointStatus: 'enabled' | 'disabled' | 'missing';
|
|
982
|
+
/** Events Stripe attempted and failed to deliver inside the window. */
|
|
983
|
+
undelivered: number;
|
|
984
|
+
/** Events Stripe emitted inside the window. Reported, never a verdict input. */
|
|
985
|
+
emitted: number;
|
|
986
|
+
/**
|
|
987
|
+
* Events our handler claimed inside the window. Reported, never a verdict
|
|
988
|
+
* input — and null when the Firestore arm could not run, which does NOT on
|
|
989
|
+
* its own make the check red: Stripe already answered the question that
|
|
990
|
+
* matters.
|
|
991
|
+
*/
|
|
992
|
+
processed: number | null;
|
|
993
|
+
/**
|
|
994
|
+
* Deliveries that answered 200 and moved NOTHING inside the window
|
|
995
|
+
* (AGL-1954) — a required event type that produced neither a committed
|
|
996
|
+
* effect nor a named deliberate skip. See `webhook-delivery.ts` for why
|
|
997
|
+
* that is a defect by construction and a legitimate no-op is not one.
|
|
998
|
+
*
|
|
999
|
+
* Null when the Firestore arm could not run, and null is NOT red on its
|
|
1000
|
+
* own: Stripe already answered the question that matters, and a health
|
|
1001
|
+
* check that reds on its own read failure trains people to ignore it.
|
|
1002
|
+
*/
|
|
1003
|
+
inert: number | null;
|
|
1004
|
+
/**
|
|
1005
|
+
* Deliveries that only landed on a RETRY inside the window (AGL-2039).
|
|
1006
|
+
*
|
|
1007
|
+
* The arm every other count here is structurally blind to.
|
|
1008
|
+
* `undelivered` comes from `delivery_success=false`, which is a TERMINAL
|
|
1009
|
+
* filter over EVENTS: an event that 400s three times and then succeeds
|
|
1010
|
+
* reads back clean and is indistinguishable from one that succeeded
|
|
1011
|
+
* immediately. The Stripe Dashboard scores delivery ATTEMPTS and counted
|
|
1012
|
+
* all three — which is why AGL-1906 read 0% against the Dashboard's 30% and
|
|
1013
|
+
* both figures were correct. The three attempts it could not see were
|
|
1014
|
+
* AGL-1551.
|
|
1015
|
+
*
|
|
1016
|
+
* The webhook stamps `retriedAtMs` when the distance from `event.created`
|
|
1017
|
+
* to its own claim exceeds `RETRY_LAG_SECONDS`, so this is an aggregation
|
|
1018
|
+
* over a marker field rather than a scan of every claim in the window.
|
|
1019
|
+
*
|
|
1020
|
+
* Null when the Firestore arm could not run, and null is NOT red on its
|
|
1021
|
+
* own — same rule as `processed` and `inert`.
|
|
1022
|
+
*/
|
|
1023
|
+
retried: number | null;
|
|
1024
|
+
/**
|
|
1025
|
+
* Required event types the destination is NOT subscribed to (AGL-1948 /
|
|
1026
|
+
* AGL-1798). Empty is the healthy answer; null means the endpoint did not
|
|
1027
|
+
* report its subscriptions and the coverage question is unanswered.
|
|
1028
|
+
*/
|
|
1029
|
+
unsubscribedEvents: readonly string[] | null;
|
|
1030
|
+
/**
|
|
1031
|
+
* The CONNECT destination, as Stripe currently holds it (AGL-1948).
|
|
1032
|
+
*
|
|
1033
|
+
* A SECOND destination, and the platform one being perfectly healthy says
|
|
1034
|
+
* nothing about it: connected-account events are delivered only to a
|
|
1035
|
+
* destination created with `connect: true`, so its absence is invisible to
|
|
1036
|
+
* every count above. AGL-2122 found exactly that — `account.updated`
|
|
1037
|
+
* handled in two plugins, and no destination that could ever deliver it,
|
|
1038
|
+
* so every merchant's `stripeChargesEnabled` rotted silently.
|
|
1039
|
+
*
|
|
1040
|
+
* Null when the endpoint census itself could not be taken.
|
|
1041
|
+
*/
|
|
1042
|
+
connectEndpoint: 'enabled' | 'disabled' | 'missing' | null;
|
|
1043
|
+
/**
|
|
1044
|
+
* Required Connect events the Connect destination is NOT subscribed to.
|
|
1045
|
+
* Empty is healthy; null means there was no destination to ask, or it did
|
|
1046
|
+
* not state its subscriptions.
|
|
1047
|
+
*/
|
|
1048
|
+
unsubscribedConnectEvents: readonly string[] | null;
|
|
1049
|
+
}
|
|
1050
|
+
export interface BillingWebhookCheck extends HealthCheck {
|
|
1051
|
+
endpointStatus: BillingWebhookFacts['endpointStatus'] | null;
|
|
1052
|
+
undelivered: number | null;
|
|
1053
|
+
emitted: number | null;
|
|
1054
|
+
processed: number | null;
|
|
1055
|
+
inert: number | null;
|
|
1056
|
+
retried: number | null;
|
|
1057
|
+
unsubscribedEvents: readonly string[] | null;
|
|
1058
|
+
connectEndpoint: BillingWebhookFacts['connectEndpoint'] | null;
|
|
1059
|
+
unsubscribedConnectEvents: readonly string[] | null;
|
|
1060
|
+
/** The trailing window the counts cover, so the body is self-describing. */
|
|
1061
|
+
windowMinutes: number;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Trailing window the delivery counts cover.
|
|
1065
|
+
*
|
|
1066
|
+
* Bounded from below by the alert path, not by taste, exactly like
|
|
1067
|
+
* `RATE_LIMIT_DEGRADED_WINDOW_MINUTES`: the probe memoizes for 5 minutes, the
|
|
1068
|
+
* uptime check runs every 5, and the policy wants ~10 minutes of sustained
|
|
1069
|
+
* failure before it emails — so a window under ~20 minutes can go red and
|
|
1070
|
+
* green again before anyone is told. Bounded from ABOVE by Stripe's retry
|
|
1071
|
+
* schedule: an event that fails and then succeeds on retry stops counting, so
|
|
1072
|
+
* a window much longer than an hour would hold transient blips red past their
|
|
1073
|
+
* own recovery.
|
|
1074
|
+
*/
|
|
1075
|
+
export declare const WEBHOOK_FAILURE_WINDOW_MINUTES = 60;
|
|
1076
|
+
/**
|
|
1077
|
+
* Failed deliveries tolerated in the window.
|
|
1078
|
+
*
|
|
1079
|
+
* Zero, and unlike the signup alarm there is no organic baseline to clear: a
|
|
1080
|
+
* healthy account produces exactly zero, and one is already the statement
|
|
1081
|
+
* "Stripe tried to tell us something about money and could not". Stripe
|
|
1082
|
+
* retries on its own, so a failure visible here is not one unlucky packet.
|
|
1083
|
+
*/
|
|
1084
|
+
export declare const MAX_FAILED_DELIVERIES_PER_WINDOW = 0;
|
|
1085
|
+
/**
|
|
1086
|
+
* Inert deliveries tolerated in the window (AGL-1954).
|
|
1087
|
+
*
|
|
1088
|
+
* Zero — and unlike `processed`, the count this check reports but refuses to
|
|
1089
|
+
* gate on because no defensible floor exists before the beta produces a
|
|
1090
|
+
* baseline, this one's floor follows from the definition. A delivery is
|
|
1091
|
+
* counted inert only when a required event type produced neither a committed
|
|
1092
|
+
* effect nor a NAMED deliberate skip (`classifyWebhookDelivery`). No
|
|
1093
|
+
* legitimate traffic pattern generates one: a tenant shopper's subscription,
|
|
1094
|
+
* a marketplace refund, a `won` dispute nobody claimed all name their reason
|
|
1095
|
+
* and classify as `ignored`. So any at all is the statement "Stripe told us
|
|
1096
|
+
* about money and nothing here moved" — the same "any at all" rule the
|
|
1097
|
+
* rate-limiter degradation markers use (AGL-1679).
|
|
1098
|
+
*/
|
|
1099
|
+
export declare const MAX_INERT_DELIVERIES_PER_WINDOW = 0;
|
|
1100
|
+
/**
|
|
1101
|
+
* Retried deliveries tolerated in the window (AGL-2039).
|
|
1102
|
+
*
|
|
1103
|
+
* Zero, and the reasoning is `MAX_FAILED_DELIVERIES_PER_WINDOW`'s: a delivery
|
|
1104
|
+
* counted here is one whose FIRST attempt did not get through, which is a
|
|
1105
|
+
* failed attempt that happened, not a statistical wobble. The healthy band
|
|
1106
|
+
* measured against the live account is 1.0–3.7 seconds against a 120-second
|
|
1107
|
+
* bar, so nothing organic sits anywhere near it.
|
|
1108
|
+
*
|
|
1109
|
+
* Cheap to be wrong in the safe direction, because this cannot latch: the
|
|
1110
|
+
* window is trailing and the marker is on the event's own claim, so a single
|
|
1111
|
+
* blip reds for at most one window and then clears itself — an event, not a
|
|
1112
|
+
* condition (the AGL-1843 rule). It is a parameter rather than a literal so
|
|
1113
|
+
* that if beta traffic turns out to produce organic retries, the knob is one
|
|
1114
|
+
* argument and not a rewrite.
|
|
1115
|
+
*/
|
|
1116
|
+
export declare const MAX_RETRIED_DELIVERIES_PER_WINDOW = 0;
|
|
1117
|
+
export declare function billingWebhookHealth(facts: BillingWebhookFacts | null, ms: number, windowMinutes?: number, threshold?: number, inertThreshold?: number, retriedThreshold?: number): BillingWebhookCheck;
|
|
1118
|
+
/**
|
|
1119
|
+
* Is every interval we can SELL also an interval we can BILL? (AGL-1931)
|
|
1120
|
+
*
|
|
1121
|
+
* The metered item is what turns reported usage into money. It is attached
|
|
1122
|
+
* from an env-configured price id that is keyed by billing interval —
|
|
1123
|
+
* `STRIPE_PRICE_METERED` for monthly, `STRIPE_PRICE_METERED_YEARLY` for
|
|
1124
|
+
* annual — because Stripe forbids one subscription mixing
|
|
1125
|
+
* `recurring.interval`.
|
|
1126
|
+
*
|
|
1127
|
+
* When one of those ids is missing, `meteredPriceId(interval)` returns null
|
|
1128
|
+
* and every subscription-mutating path does the only safe thing: it attaches
|
|
1129
|
+
* nothing and carries on. Failing a checkout over it would be worse. But the
|
|
1130
|
+
* result is a subscription that accrues usage against a meter and carries no
|
|
1131
|
+
* item to bill it, and NOTHING about that is visible — the plan is right, the
|
|
1132
|
+
* entitlements are right, the invoice looks right, and the only trace is
|
|
1133
|
+
* revenue that never arrives.
|
|
1134
|
+
*
|
|
1135
|
+
* That is the defect shape this check exists to end. The absence of a price
|
|
1136
|
+
* was indistinguishable from "correctly no overage", so it hid behind a
|
|
1137
|
+
* `console.warn` on a server log nobody reads. Here it is a 503 on the same
|
|
1138
|
+
* probe the uptime check and alert email already watch.
|
|
1139
|
+
*
|
|
1140
|
+
* ## Why ASYMMETRY is the loudest code
|
|
1141
|
+
*
|
|
1142
|
+
* One interval configured and the other not means one cohort of customers is
|
|
1143
|
+
* billed for overage and the other silently is not — off the same meter, on
|
|
1144
|
+
* the same plans, with no screen anywhere showing the difference. It cannot
|
|
1145
|
+
* be a deliberate state: there is no product reason to meter monthly
|
|
1146
|
+
* subscribers and exempt annual ones.
|
|
1147
|
+
*
|
|
1148
|
+
* ## Why BOTH missing is still red, but only with Stripe configured
|
|
1149
|
+
*
|
|
1150
|
+
* Both unset with no `STRIPE_SECRET_KEY` is simply an unprovisioned
|
|
1151
|
+
* deployment — local dev, a fresh preview — where no money is at stake and
|
|
1152
|
+
* going red would train everyone to ignore this check. That is the exact
|
|
1153
|
+
* reasoning the checkout and subscription routes already apply to their
|
|
1154
|
+
* warning, and it is preserved here.
|
|
1155
|
+
*
|
|
1156
|
+
* But both unset while Stripe IS configured is a deployment that can take
|
|
1157
|
+
* money and cannot bill a single unit of overage on ANY interval. That is
|
|
1158
|
+
* the same revenue loss, merely total instead of partial, and the earlier
|
|
1159
|
+
* code treated it as calm because it only ever compared the two ids to each
|
|
1160
|
+
* other. Comparing them to Stripe's presence instead is what closes it.
|
|
1161
|
+
*/
|
|
1162
|
+
export interface MeteredPricingFacts {
|
|
1163
|
+
/** Whether this deployment holds a Stripe secret key at all. */
|
|
1164
|
+
stripeConfigured: boolean;
|
|
1165
|
+
/** `STRIPE_PRICE_METERED` — the monthly metered price — is set. */
|
|
1166
|
+
monthly: boolean;
|
|
1167
|
+
/** `STRIPE_PRICE_METERED_YEARLY` — the annual metered price — is set. */
|
|
1168
|
+
yearly: boolean;
|
|
1169
|
+
}
|
|
1170
|
+
export interface MeteredPricingCheck extends HealthCheck {
|
|
1171
|
+
stripeConfigured: boolean;
|
|
1172
|
+
monthly: boolean;
|
|
1173
|
+
yearly: boolean;
|
|
1174
|
+
/**
|
|
1175
|
+
* The interval that would accrue unbilled usage, named so the body says
|
|
1176
|
+
* which cohort is affected without anyone re-deriving it from two booleans.
|
|
1177
|
+
*/
|
|
1178
|
+
unbilledInterval: 'month' | 'year' | 'both' | null;
|
|
1179
|
+
}
|
|
1180
|
+
export declare function meteredPricingHealth(facts: MeteredPricingFacts, ms: number): MeteredPricingCheck;
|
|
1181
|
+
/** Where the marks live. One document per job id, written by the job. */
|
|
1182
|
+
export declare const CRON_BEAT_COLLECTION = "platformCronBeats";
|
|
1183
|
+
/**
|
|
1184
|
+
* The document that records when this deployment STARTED watching.
|
|
1185
|
+
*
|
|
1186
|
+
* Without it a job that has never reported cannot be told apart from a job
|
|
1187
|
+
* that has stopped reporting, and every board would come up red on the day
|
|
1188
|
+
* the feature deploys — for `usage-email`, red until the 1st of the next
|
|
1189
|
+
* month. The rule this enables is stated once, in `cronJobsHealth`: a job
|
|
1190
|
+
* with no mark is only silent once a scheduled fire time has passed SINCE
|
|
1191
|
+
* we started watching.
|
|
1192
|
+
*
|
|
1193
|
+
* Not a reserved id: Firestore reserves `__.*__`, and this is a plain slug
|
|
1194
|
+
* that no job id can collide with (job ids never contain the word `watch`,
|
|
1195
|
+
* and the wiring spec asserts the inventory's ids against the workflow).
|
|
1196
|
+
*/
|
|
1197
|
+
export declare const CRON_BEAT_WATCH_DOC = "watch-window";
|
|
1198
|
+
/**
|
|
1199
|
+
* Where a degraded verdict is recorded, so a red window survives it.
|
|
1200
|
+
*
|
|
1201
|
+
* The response body names the late job, but only to whoever is holding it,
|
|
1202
|
+
* and the uptime probe reads the status and discards the body. Vercel's
|
|
1203
|
+
* runtime log keeps a `console.error` for about an hour, and the Cloud
|
|
1204
|
+
* Logging drain deliberately does not forward one — `isServerErrorEntry`
|
|
1205
|
+
* drops `level: 'error'`, because that is every fail-soft line the beacons
|
|
1206
|
+
* write and forwarding it is what a self-feeding bill is made of. So the
|
|
1207
|
+
* only sink that outlives the window is the one the endpoint already holds
|
|
1208
|
+
* a handle to.
|
|
1209
|
+
*
|
|
1210
|
+
* Written by the READER, like the verdict itself. One document, replaced on
|
|
1211
|
+
* each degraded probe, holding the failing rows and when the window opened.
|
|
1212
|
+
* Same collision argument as the watch document: a plain slug, and no job id
|
|
1213
|
+
* contains `degraded`.
|
|
1214
|
+
*/
|
|
1215
|
+
export declare const CRON_BEAT_DEGRADED_DOC = "last-degraded";
|
|
1216
|
+
/**
|
|
1217
|
+
* Every job's latest mark in one document, `{ beats: { [jobId]: atMs } }`.
|
|
1218
|
+
*
|
|
1219
|
+
* The reader's fast path (AGL-2946). Listing the collection costs one read per
|
|
1220
|
+
* job on every uncached probe — ~23 reads, several hundred times a day, on a
|
|
1221
|
+
* project that pays for reads past the free tier — to answer a question this
|
|
1222
|
+
* one document answers.
|
|
1223
|
+
*
|
|
1224
|
+
* A COPY, never the authority. `writeCronBeat` writes it second, so a job
|
|
1225
|
+
* whose own mark landed can still be missing or stale here, and the reader
|
|
1226
|
+
* confirms any job this document calls late against that job's own mark
|
|
1227
|
+
* before reporting it. Stale costs a read; it never costs a false red.
|
|
1228
|
+
*
|
|
1229
|
+
* Same collision argument as the other two: a plain slug no job id equals.
|
|
1230
|
+
*/
|
|
1231
|
+
export declare const CRON_BEAT_SUMMARY_DOC = "summary";
|
|
1232
|
+
/** The reserved documents in `CRON_BEAT_COLLECTION` that are not job marks. */
|
|
1233
|
+
export declare const CRON_BEAT_RESERVED_DOCS: ReadonlySet<string>;
|
|
1234
|
+
/**
|
|
1235
|
+
* How long a gap may be before the next degraded probe counts as a NEW
|
|
1236
|
+
* window rather than a continuation of the one on file.
|
|
1237
|
+
*
|
|
1238
|
+
* Three probe TTLs. Long enough that an instance answering intermittently
|
|
1239
|
+
* does not split one outage into a dozen, short enough that yesterday's
|
|
1240
|
+
* window is never mistaken for today's. The comparison is against the
|
|
1241
|
+
* failing set as well, so a different set always opens a new window.
|
|
1242
|
+
*/
|
|
1243
|
+
export declare const CRON_DEGRADED_WINDOW_CONTINUITY_MS: number;
|
|
1244
|
+
/** Who fires the job. The two are operated, and fail, differently. */
|
|
1245
|
+
export type CronRunner = 'github-actions' | 'cloud-scheduler';
|
|
1246
|
+
export interface ScheduledJob {
|
|
1247
|
+
/** Stable id. The Firestore document id, and the health check's name. */
|
|
1248
|
+
id: string;
|
|
1249
|
+
label: string;
|
|
1250
|
+
/**
|
|
1251
|
+
* Five-field cron, UTC. The SAME string the scheduler holds — the wiring
|
|
1252
|
+
* spec asserts each one against its runner's source of truth
|
|
1253
|
+
* (`.github/workflows/scheduled-crons.yml` for `github-actions`,
|
|
1254
|
+
* `cloud/functions/src/index.ts` for `cloud-scheduler`), so an edit there
|
|
1255
|
+
* that is not made here fails the build rather than quietly changing what
|
|
1256
|
+
* "on time" means.
|
|
1257
|
+
*/
|
|
1258
|
+
cron: string;
|
|
1259
|
+
runner: CronRunner;
|
|
1260
|
+
/** What the scheduler actually invokes, for the operator reading a red row. */
|
|
1261
|
+
target: string;
|
|
1262
|
+
/**
|
|
1263
|
+
* How late a run may be before the row goes red.
|
|
1264
|
+
*
|
|
1265
|
+
* Generous on purpose, and the generosity is not padding. GitHub only runs
|
|
1266
|
+
* scheduled workflows from the default branch and delays them under load —
|
|
1267
|
+
* routinely by minutes, occasionally by an hour. The question this check
|
|
1268
|
+
* answers is "is this job still scheduled at all", not "did it start on the
|
|
1269
|
+
* second"; a grace that reds on ordinary lateness is alert fatigue, and an
|
|
1270
|
+
* alert people learn to ignore is the failure this issue is about wearing
|
|
1271
|
+
* a different hat.
|
|
1272
|
+
*
|
|
1273
|
+
* SO THE GRACE IS A PROPERTY OF THE RUNNER, not of the job (AGL-1617). The
|
|
1274
|
+
* ninety minutes the two frequent sweeps used to carry was GitHub's drift
|
|
1275
|
+
* budget, and it was bought with detection: on a fifteen-minute schedule it
|
|
1276
|
+
* takes SIX consecutive missed fires before anything is said. Moving those
|
|
1277
|
+
* two onto Cloud Scheduler — which is punctual, as `plugin-jobs-beat` has
|
|
1278
|
+
* been demonstrating at `every 1 minutes` in the same payload the whole
|
|
1279
|
+
* time — is what let the bar go back UP, to forty-five minutes and three
|
|
1280
|
+
* missed fires. That direction is the point: widening a grace to make a
|
|
1281
|
+
* monitor agree with a runner that drops 60% of its triggers is not a fix,
|
|
1282
|
+
* it is deciding not to look.
|
|
1283
|
+
*
|
|
1284
|
+
* IT IS A FLOOR, NOT THE EXACT BAR. `cronJobsHealth` compares the mark
|
|
1285
|
+
* against the last fire that is ALREADY this old, so where the threshold
|
|
1286
|
+
* lands depends on the clock's phase against the schedule: forty-five
|
|
1287
|
+
* minutes on a fifteen-minute cron reds somewhere between 45 and 60 minutes
|
|
1288
|
+
* of silence, six hours on a daily one between 6h and 30h. Quote the range,
|
|
1289
|
+
* not the number, when reasoning about how fast a dead job is found — and
|
|
1290
|
+
* never write a test that only holds at one phase.
|
|
1291
|
+
*/
|
|
1292
|
+
graceMinutes: number;
|
|
1293
|
+
/** What stops happening when this job stops. Rendered on the board. */
|
|
1294
|
+
drives: string;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* THE INVENTORY.
|
|
1298
|
+
*
|
|
1299
|
+
* Seven GitHub Actions schedules (`.github/workflows/scheduled-crons.yml`) —
|
|
1300
|
+
* the weekly jobs, the month-boundary usage-email sweep and the hourly CRM
|
|
1301
|
+
* task reminders, for which an hour of drift is nothing — and the rows
|
|
1302
|
+
* driven by Cloud Scheduler out of
|
|
1303
|
+
* `cloud/functions/src/index.ts`: `pluginJobsBeat` and `consoleAiJobsBeat`
|
|
1304
|
+
* (every minute), the routes the `consoleFastCrons` job carries every
|
|
1305
|
+
* fifteen (AGL-1617), and one `consoleDailyCron` export per daily job.
|
|
1306
|
+
*
|
|
1307
|
+
* `scheduled-crons-wiring.spec.ts` holds BOTH runners against their source —
|
|
1308
|
+
* the workflow for the `github-actions` rows, the functions file for the
|
|
1309
|
+
* `cloud-scheduler` ones — in both directions. An inventory row for a job
|
|
1310
|
+
* nobody schedules and a scheduled job nobody watches are the same bug seen
|
|
1311
|
+
* from opposite ends, and both fail the build.
|
|
1312
|
+
*
|
|
1313
|
+
* `report-usage` appears TWICE and is two jobs, not one. The 02:00 run
|
|
1314
|
+
* rolls up the closed month and is the only run that ever reaches Stripe;
|
|
1315
|
+
* the 07:00 `?month=current` run writes the in-progress figure every usage
|
|
1316
|
+
* budget reads (AGL-2219). Either can stop without the other, and folding
|
|
1317
|
+
* them into one row would hide exactly that.
|
|
1318
|
+
*/
|
|
1319
|
+
export declare const SCHEDULED_JOBS: readonly ScheduledJob[];
|
|
1320
|
+
/** One job's most recent mark, as read from `CRON_BEAT_COLLECTION`. */
|
|
1321
|
+
export interface CronBeat {
|
|
1322
|
+
jobId: string;
|
|
1323
|
+
/** Epoch ms of the invocation that left the mark. */
|
|
1324
|
+
atMs: number;
|
|
1325
|
+
}
|
|
1326
|
+
export interface CronJobCheck extends HealthCheck {
|
|
1327
|
+
/** The schedule this row was judged against, so the body is self-describing. */
|
|
1328
|
+
schedule: string;
|
|
1329
|
+
runner: CronRunner;
|
|
1330
|
+
/** Minutes since the job last reported. Null when it never has. */
|
|
1331
|
+
lastBeatAgeMinutes: number | null;
|
|
1332
|
+
/**
|
|
1333
|
+
* The most recent fire time the job should already have reported for, ISO.
|
|
1334
|
+
* Null when no such time has passed since we started watching — which is
|
|
1335
|
+
* the healthy reading for a job that is legitimately idle.
|
|
1336
|
+
*/
|
|
1337
|
+
dueAt: string | null;
|
|
1338
|
+
graceMinutes: number;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* The latest time at or before `atMs` that `expression` fires, UTC.
|
|
1342
|
+
*
|
|
1343
|
+
* Null when the expression is unparsable, or when it does not fire inside
|
|
1344
|
+
* the lookback — 45 days, which comfortably clears the longest gap any
|
|
1345
|
+
* inventory entry has (`usage-email`'s twenty-nine idle days).
|
|
1346
|
+
*
|
|
1347
|
+
* Whole non-matching DAYS are skipped rather than walked minute by minute,
|
|
1348
|
+
* so the worst case is ~45 day checks plus 1,440 minute checks rather than
|
|
1349
|
+
* 64,800 of them.
|
|
1350
|
+
*/
|
|
1351
|
+
export declare function previousCronFire(expression: string, atMs: number, lookbackDays?: number): number | null;
|
|
1352
|
+
/**
|
|
1353
|
+
* The scheduled jobs, reduced to one health check per job (AGL-1955).
|
|
1354
|
+
*
|
|
1355
|
+
* `beats` null means the marks could not be read at all — degraded for every
|
|
1356
|
+
* row, the same rule `signupsHealth`, `rateLimitsHealth` and `beaconHealth`
|
|
1357
|
+
* follow. An alarm that cannot see the thing it watches must say so rather
|
|
1358
|
+
* than report calm; here it is the literal condition the issue is about.
|
|
1359
|
+
*
|
|
1360
|
+
* `watchStartedAtMs` is when this deployment first read the marks. A job with
|
|
1361
|
+
* no mark is silent only once a fire time has passed SINCE then — otherwise
|
|
1362
|
+
* every row would be red on the day this deploys, and `usage-email` would
|
|
1363
|
+
* stay red until the 1st.
|
|
1364
|
+
*
|
|
1365
|
+
* Pure on purpose, like its siblings: the route reads, this decides, the
|
|
1366
|
+
* spec exercises every branch without a network.
|
|
1367
|
+
*/
|
|
1368
|
+
export declare function cronJobsHealth(beats: readonly CronBeat[] | null, watchStartedAtMs: number, ms: number, now?: number, jobs?: readonly ScheduledJob[]): Record<string, CronJobCheck>;
|
|
1369
|
+
/**
|
|
1370
|
+
* The mark a run leaves — the only writes on the beat path.
|
|
1371
|
+
*
|
|
1372
|
+
* Structurally typed against firebase-admin's Firestore rather than importing
|
|
1373
|
+
* it: this module is pure and is imported by tenant Server Components, and a
|
|
1374
|
+
* health verdict library that drags in the admin SDK would be a new reason
|
|
1375
|
+
* for a page to fail. The console routes, the marketing plugin's scheduled
|
|
1376
|
+
* campaign processor and the tenant job runner all pass their own handle.
|
|
1377
|
+
*
|
|
1378
|
+
* Two writes, in order: the job's own document, which is the authority, then
|
|
1379
|
+
* its entry in {@link CRON_BEAT_SUMMARY_DOC}, which is what the reader reads
|
|
1380
|
+
* first. The summary merges one map key, so jobs beating at the same moment
|
|
1381
|
+
* do not overwrite each other's entries.
|
|
1382
|
+
*
|
|
1383
|
+
* NEVER THROWS. A beat that cannot be written must not take down the job it
|
|
1384
|
+
* is describing — the monitor becoming the outage is its own failure mode.
|
|
1385
|
+
* It returns false when the job's own mark could not be written, and a write
|
|
1386
|
+
* that keeps failing shows up where it should: as a silent job on the board.
|
|
1387
|
+
* A failed summary write still returns true, because the mark that decides
|
|
1388
|
+
* the verdict landed.
|
|
1389
|
+
*/
|
|
1390
|
+
export interface CronBeatStore {
|
|
1391
|
+
collection(name: string): {
|
|
1392
|
+
doc(id: string): {
|
|
1393
|
+
set(data: Record<string, unknown>, options?: unknown): Promise<unknown>;
|
|
1394
|
+
};
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
export declare function writeCronBeat(store: CronBeatStore, jobId: string, now?: number): Promise<boolean>;
|
|
1398
|
+
/**
|
|
1399
|
+
* The beats a summary document holds, ignoring anything malformed.
|
|
1400
|
+
*
|
|
1401
|
+
* `raw` is the document's `beats` field as Firestore returns it — a plain map,
|
|
1402
|
+
* or undefined when the document does not exist yet.
|
|
1403
|
+
*/
|
|
1404
|
+
export declare function cronBeatsFromSummary(raw: unknown): CronBeat[];
|
|
1405
|
+
/**
|
|
1406
|
+
* DID A STRANGER ACTUALLY SIGN UP? (AGL-2715)
|
|
1407
|
+
*
|
|
1408
|
+
* Every other signup check on this platform is an inference. The auth doors
|
|
1409
|
+
* assert that Identity Platform is reachable and refuses an account that
|
|
1410
|
+
* cannot exist — configuration, not a signup. `probeCreate` reads that the
|
|
1411
|
+
* platform is unlocked and the probe's name is free — preconditions, not a
|
|
1412
|
+
* signup. `signupVolume`, `signupRefusals` and `signupDrought` count what
|
|
1413
|
+
* happened afterwards — a consequence, observed late.
|
|
1414
|
+
*
|
|
1415
|
+
* The strongest thing any of them could say, on the day AGL-2714 paged, was
|
|
1416
|
+
* "somebody got an account recently", and recently was 24 hours ago. AGL-2581
|
|
1417
|
+
* is what that costs: signup refused every visitor for three days while the
|
|
1418
|
+
* monitor named for signups stayed green.
|
|
1419
|
+
*
|
|
1420
|
+
* So something has to WALK it. A canary creates an account, verifies it,
|
|
1421
|
+
* mints a session, provisions an org, and deletes all of it — against
|
|
1422
|
+
* production, on a schedule — then stamps what happened into one marker. This
|
|
1423
|
+
* verdict reads that marker.
|
|
1424
|
+
*
|
|
1425
|
+
* ## Why the walk is not done here
|
|
1426
|
+
*
|
|
1427
|
+
* Health probes on this platform are read-only and cost-bounded by contract,
|
|
1428
|
+
* and this one is served from a PUBLIC unauthenticated endpoint. A probe that
|
|
1429
|
+
* created an account would hand anyone with `curl` an org factory. The write
|
|
1430
|
+
* lives in a scheduled job; the door only reports. Same split as the beacon
|
|
1431
|
+
* heartbeat (AGL-2713), for the same reason.
|
|
1432
|
+
*
|
|
1433
|
+
* ## The four ways this reds, and why residue is one of them
|
|
1434
|
+
*
|
|
1435
|
+
* A canary that walks perfectly and fails to clean up is not a success. It
|
|
1436
|
+
* leaves a real org, with a real slug reservation and a real host, on the
|
|
1437
|
+
* production estate — every hour, forever. `canary-left-residue` is red even
|
|
1438
|
+
* though the signup itself worked, because the alternative is a monitor that
|
|
1439
|
+
* quietly fills the database it is monitoring.
|
|
1440
|
+
*/
|
|
1441
|
+
export interface SignupCanaryMarker {
|
|
1442
|
+
/** When the walk finished. The freshness clock. */
|
|
1443
|
+
walkedAtMs?: number;
|
|
1444
|
+
/** Whether every step of the walk succeeded. */
|
|
1445
|
+
ok?: boolean;
|
|
1446
|
+
/** Which step broke, for the body. Never an error message, never a uid. */
|
|
1447
|
+
failedStep?: string | null;
|
|
1448
|
+
/** Wall clock for the whole walk. */
|
|
1449
|
+
elapsedMs?: number;
|
|
1450
|
+
/** Whether the walk deleted everything it created. */
|
|
1451
|
+
reapedCleanly?: boolean;
|
|
1452
|
+
}
|
|
1453
|
+
export interface SignupCanaryCheck extends HealthCheck {
|
|
1454
|
+
/** Age of the last recorded walk, or null when there is no marker. */
|
|
1455
|
+
ageMs: number | null;
|
|
1456
|
+
/** Which step broke on that walk, when one did. */
|
|
1457
|
+
failedStep: string | null;
|
|
1458
|
+
/** How long the walk took, for the latency graph. */
|
|
1459
|
+
walkMs: number | null;
|
|
1460
|
+
/** Whether it cleaned up after itself. */
|
|
1461
|
+
reapedCleanly: boolean | null;
|
|
1462
|
+
/** The age past which a walk stops counting as evidence. */
|
|
1463
|
+
staleAfterMs: number;
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* How old the last successful walk may be before this reds.
|
|
1467
|
+
*
|
|
1468
|
+
* Two hours against an hourly schedule — one missed run is tolerated, two are
|
|
1469
|
+
* not. A canary that runs hourly and reds at 61 minutes would page on every
|
|
1470
|
+
* GitHub Actions queue delay, and an alarm that cries wolf on infrastructure
|
|
1471
|
+
* latency is one nobody reads by the time signup actually breaks. Two misses
|
|
1472
|
+
* is a pattern rather than a hiccup.
|
|
1473
|
+
*
|
|
1474
|
+
* This is deliberately NOT generous enough to sleep through AGL-2581: three
|
|
1475
|
+
* days of refused signups would red this inside two hours.
|
|
1476
|
+
*/
|
|
1477
|
+
/**
|
|
1478
|
+
* Workspace-slug prefix the signup canary owns, and the one piece of the
|
|
1479
|
+
* product that has to know the canary exists (AGL-2715).
|
|
1480
|
+
*
|
|
1481
|
+
* ## Why an exclusion is not optional
|
|
1482
|
+
*
|
|
1483
|
+
* The canary creates a real org and then deletes it. `signupDrought` compares
|
|
1484
|
+
* durable ATTEMPT markers against a live COUNT of orgs — and a deleted org
|
|
1485
|
+
* leaves the count while its attempt marker stays. So every canary walk adds
|
|
1486
|
+
* 1 to the denominator and 0 to the numerator, and after three walks the
|
|
1487
|
+
* platform reports `signup-drought` with nothing wrong.
|
|
1488
|
+
*
|
|
1489
|
+
* That is not theoretical. It fired on 2026-09-09 at six attempts against
|
|
1490
|
+
* zero creations — six walks that had each just proved, end to end, that a
|
|
1491
|
+
* stranger could sign up. A monitor that manufactures the outage it watches
|
|
1492
|
+
* for is worse than no monitor, and it is the same alarm fatigue AGL-2714
|
|
1493
|
+
* removed, arriving from inside the house.
|
|
1494
|
+
*
|
|
1495
|
+
* ## Why the slug, and what that costs
|
|
1496
|
+
*
|
|
1497
|
+
* `/api/orgs/create` receives the requested slug before it does anything, so
|
|
1498
|
+
* this needs no secret, no header and no second code path — the canary is
|
|
1499
|
+
* excluded by being what it says it is.
|
|
1500
|
+
*
|
|
1501
|
+
* The cost is that the prefix is caller-supplied: someone naming their
|
|
1502
|
+
* workspace `signup-canary-…` would not be counted in the drought's
|
|
1503
|
+
* denominator. That is bounded to one uncounted attempt per such workspace,
|
|
1504
|
+
* and it cannot hide an outage, because an outage is many attempts by many
|
|
1505
|
+
* people and only the oddly-named ones would go missing.
|
|
1506
|
+
*
|
|
1507
|
+
* The canary declares the same prefix in `tools/e2e/signup-canary.mjs`, which
|
|
1508
|
+
* cannot import this file — it runs outside the workspace. They are held
|
|
1509
|
+
* together by `apps/console/specs/signup-canary-marker-wiring.spec.ts`.
|
|
1510
|
+
*/
|
|
1511
|
+
export declare const SIGNUP_CANARY_ORG_SLUG_PREFIX = "signup-canary-";
|
|
1512
|
+
/**
|
|
1513
|
+
* Is this org creation the canary's own?
|
|
1514
|
+
*
|
|
1515
|
+
* Exported as a function rather than leaving callers to compare strings, so
|
|
1516
|
+
* the one place that decides is the one place to read when asking why a
|
|
1517
|
+
* number looks wrong.
|
|
1518
|
+
*/
|
|
1519
|
+
export declare function isSignupCanaryOrgSlug(slug: string | null | undefined): boolean;
|
|
1520
|
+
/**
|
|
1521
|
+
* How old the last walk may be before the check goes red.
|
|
1522
|
+
*
|
|
1523
|
+
* Six hours, far wider than the schedule that writes it, and deliberately so.
|
|
1524
|
+
* GitHub Actions serves `schedule` from a best-effort queue that drops most
|
|
1525
|
+
* requests under load: measured 2026-09-10, `uptime-probe.yml` asks for every
|
|
1526
|
+
* fifteen minutes and actually fired at gaps of 1h55m to 4h38m across the
|
|
1527
|
+
* preceding day and a half. A window sized to the cron expression rather than
|
|
1528
|
+
* to that behavior reds on ordinary queue depth.
|
|
1529
|
+
*
|
|
1530
|
+
* Widening it costs no detection speed, because staleness is not how a broken
|
|
1531
|
+
* signup surfaces. A walk that cannot sign up FAILS and still writes its
|
|
1532
|
+
* marker, which reads `signup-walk-failed` on the next poll. Staleness can
|
|
1533
|
+
* only mean the walker never reported — a CI condition, not a product one —
|
|
1534
|
+
* so this window's whole job is to notice a canary that has genuinely stopped,
|
|
1535
|
+
* and it must not fire before then: this check grades the monitor that must
|
|
1536
|
+
* never be muted.
|
|
1537
|
+
*/
|
|
1538
|
+
export declare const SIGNUP_CANARY_STALE_AFTER_MS: number;
|
|
1539
|
+
/**
|
|
1540
|
+
* Grade the last recorded signup walk.
|
|
1541
|
+
*
|
|
1542
|
+
* Pure, like every sibling: the route reads the marker, this decides. Ordered
|
|
1543
|
+
* by which mistake costs most — a missing marker is treated as failure, never
|
|
1544
|
+
* as calm, because "no evidence" and "evidence of success" are the two
|
|
1545
|
+
* readings this whole issue exists to stop anyone from confusing.
|
|
1546
|
+
*/
|
|
1547
|
+
export declare function signupCanaryHealth(marker: SignupCanaryMarker | null, ms: number, nowMs?: number, staleAfterMs?: number): SignupCanaryCheck;
|
|
1548
|
+
/**
|
|
1549
|
+
* IS ATTESTATION WORKING FOR REAL PEOPLE? (AGL-2715)
|
|
1550
|
+
*
|
|
1551
|
+
* The signup canary walks the real journey, but it attests with an App Check
|
|
1552
|
+
* DEBUG TOKEN — because this project's provider is reCAPTCHA Enterprise, which
|
|
1553
|
+
* is built to score headless automation as a bot and always will. Everything
|
|
1554
|
+
* else in that walk is real; the attestation step is not. So the canary is
|
|
1555
|
+
* structurally blind to the one failure it sits closest to: App Check refusing
|
|
1556
|
+
* the people it is supposed to admit.
|
|
1557
|
+
*
|
|
1558
|
+
* That blindness is not acceptable on its own, and it is not fixed by
|
|
1559
|
+
* argument. It is fixed by measuring the thing directly.
|
|
1560
|
+
*
|
|
1561
|
+
* ## What this reads, and why a token cannot fool it
|
|
1562
|
+
*
|
|
1563
|
+
* Firebase publishes `firebaseappcheck.googleapis.com/services/verification_count`
|
|
1564
|
+
* to Cloud Monitoring, labelled by `result` (ALLOW / DENY) and `security`
|
|
1565
|
+
* (VALID / INVALID / MISSING_UNKNOWN_ORIGIN), per service. Those counts are
|
|
1566
|
+
* every request from every visitor.
|
|
1567
|
+
*
|
|
1568
|
+
* The canary contributes at most one verification an hour against roughly a
|
|
1569
|
+
* thousand a day, and its debug-token attestation lands in the same ALLOW
|
|
1570
|
+
* bucket as everyone else's. So it cannot hold this green: if attestation
|
|
1571
|
+
* collapses for real people the rate collapses with it, one canary request
|
|
1572
|
+
* notwithstanding.
|
|
1573
|
+
*
|
|
1574
|
+
* ## Why only Identity Platform is graded
|
|
1575
|
+
*
|
|
1576
|
+
* `identitytoolkit.googleapis.com` is the service signup depends on, and it
|
|
1577
|
+
* runs clean — 939 ALLOW against 3 DENY over the 24 hours to 2026-09-09.
|
|
1578
|
+
* `firestore.googleapis.com` in the same window was 5493 ALLOW against 1046
|
|
1579
|
+
* DENY, because client Firestore reads are reached by crawlers and stale tabs
|
|
1580
|
+
* that legitimately fail attestation. Grading that number would either mute
|
|
1581
|
+
* this check with a floor low enough to admit the noise, or page on the noise.
|
|
1582
|
+
* The other services are reported for diagnosis and decide nothing.
|
|
1583
|
+
*/
|
|
1584
|
+
export interface AppCheckVerificationSample {
|
|
1585
|
+
/** `ALLOW` or `DENY`. */
|
|
1586
|
+
result?: string;
|
|
1587
|
+
/** `VALID`, `INVALID`, `MISSING_UNKNOWN_ORIGIN`, … — for the body only. */
|
|
1588
|
+
security?: string;
|
|
1589
|
+
/** The service the verdict was rendered for. */
|
|
1590
|
+
service?: string;
|
|
1591
|
+
/** Requests in this bucket over the window. */
|
|
1592
|
+
count?: number;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* IS THE EDGE STILL LETTING REAL VISITORS IN? (AGL-2720)
|
|
1596
|
+
*
|
|
1597
|
+
* Every synthetic on this platform carries the `x-aglyn-probe` firewall
|
|
1598
|
+
* bypass, because bot protection refuses a datacenter IP outright. So if the
|
|
1599
|
+
* edge started challenging real people, every check would ride past the thing
|
|
1600
|
+
* that was broken and stay green.
|
|
1601
|
+
*
|
|
1602
|
+
* Measured 2026-09-10 while looking for something to close that: Vercel
|
|
1603
|
+
* publishes no challenged-versus-allowed metric — seven endpoints probed, the
|
|
1604
|
+
* only one that answers returns rule actions rather than traffic. And a probe
|
|
1605
|
+
* cannot substitute, because the discriminator is not "was this request
|
|
1606
|
+
* challenged" but "can a REAL BROWSER get in": `curl` from a residential
|
|
1607
|
+
* address is challenged too, and that is the healthy state, since the
|
|
1608
|
+
* challenge is answerable and a non-JS client cannot answer it.
|
|
1609
|
+
*
|
|
1610
|
+
* What is left is the outcome, and it was already being counted for money.
|
|
1611
|
+
* Metered page views are written by real browsers on real customer sites
|
|
1612
|
+
* after the edge admitted them — 57 to 563 a day across hosts over the
|
|
1613
|
+
* preceding week. Nothing synthetic touches that number: the probes ride the
|
|
1614
|
+
* bypass and the signup canary walks the console, neither of which is a
|
|
1615
|
+
* metered tenant page view.
|
|
1616
|
+
*
|
|
1617
|
+
* So the question this asks is simply whether that number is still moving. A
|
|
1618
|
+
* platform serving tens of views an hour that serves none for hours has
|
|
1619
|
+
* either lost its edge or lost every customer site, and both are worth waking
|
|
1620
|
+
* somebody for.
|
|
1621
|
+
*
|
|
1622
|
+
* ⛔ What it is NOT. It watches the TENANT edge, because that is where the
|
|
1623
|
+
* volume is. The console's own front door serves a handful of signup views a
|
|
1624
|
+
* day — too few to floor, and now partly synthetic — so a firewall change
|
|
1625
|
+
* scoped only to `aglyn-console` would not show up here. It is also not a
|
|
1626
|
+
* latency or correctness check: it says traffic arrives, nothing more.
|
|
1627
|
+
*
|
|
1628
|
+
* ⚠️ And the volume is CONCENTRATED. On the day this was built, one host of
|
|
1629
|
+
* thirteen produced every view. So this reds if that one site goes quiet for
|
|
1630
|
+
* half a day, which is why the window is what it is — see
|
|
1631
|
+
* {@link EDGE_ADMISSION_QUIET_AFTER_MS}.
|
|
1632
|
+
*/
|
|
1633
|
+
/**
|
|
1634
|
+
* What the metered-traffic sampler leaves behind.
|
|
1635
|
+
*
|
|
1636
|
+
* `advancedAtMs` is the whole point and it is the WRITER's judgement, not the
|
|
1637
|
+
* reader's: the sampler compares this reading against the last one and stamps
|
|
1638
|
+
* the moment the total actually grew. A day rollover sets the counter back to
|
|
1639
|
+
* zero, so a grader handed a single sample could not tell midnight from an
|
|
1640
|
+
* outage.
|
|
1641
|
+
*/
|
|
1642
|
+
export interface EdgeAdmissionMarker {
|
|
1643
|
+
/** When the sampler last ran. Context; the verdict does not use it. */
|
|
1644
|
+
sampledAtMs?: number;
|
|
1645
|
+
/** The last moment the metered total was seen to GROW. The clock. */
|
|
1646
|
+
advancedAtMs?: number;
|
|
1647
|
+
/** Which UTC day the total belongs to. */
|
|
1648
|
+
day?: string;
|
|
1649
|
+
/** The running total for that day at the last sample. */
|
|
1650
|
+
total?: number;
|
|
1651
|
+
}
|
|
1652
|
+
export interface EdgeAdmissionCheck extends HealthCheck {
|
|
1653
|
+
/** How long since the metered total last moved, or null with no marker. */
|
|
1654
|
+
quietMs: number | null;
|
|
1655
|
+
/** The day the sampled total belongs to, so a rollover is readable. */
|
|
1656
|
+
day: string | null;
|
|
1657
|
+
/** The running total for that day at the last sample. */
|
|
1658
|
+
total: number | null;
|
|
1659
|
+
/** How long the total may sit still before this reds. */
|
|
1660
|
+
quietAfterMs: number;
|
|
1661
|
+
/** How long since the sampler last reported, or null with no marker. */
|
|
1662
|
+
sampleAgeMs: number | null;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* How long metered page views may sit still before the edge is presumed shut.
|
|
1666
|
+
*
|
|
1667
|
+
* Twelve hours, and the width is deliberate rather than lazy.
|
|
1668
|
+
*
|
|
1669
|
+
* The quietest full day in the sampled week carried 57 views — about 2.4 an
|
|
1670
|
+
* hour on average, which would make six hours of zero look damning. But the
|
|
1671
|
+
* average is the wrong statistic here: measured 2026-09-10, the day's 62 views
|
|
1672
|
+
* came from ONE host of thirteen. Traffic that concentrated arrives in bursts,
|
|
1673
|
+
* and a burst distribution produces long empty stretches with nothing wrong.
|
|
1674
|
+
* A window sized to the average would page on one of them, which is how the
|
|
1675
|
+
* monitor that must never be ignored gets ignored.
|
|
1676
|
+
*
|
|
1677
|
+
* Twelve hours of exactly zero, on a platform serving 57 to 563 views a day,
|
|
1678
|
+
* is not a quiet stretch. It is slower to fire than anyone would like, and it
|
|
1679
|
+
* is the only thing that catches a fully blocked edge at all — nothing else
|
|
1680
|
+
* would ever fire, because every other check rides the bypass.
|
|
1681
|
+
*
|
|
1682
|
+
* ⚑ Tighten it once the real quiet-period distribution is known: the sampler
|
|
1683
|
+
* stamps `advancedAtMs` on every run, so a week of markers says what the true
|
|
1684
|
+
* maximum gap is. Do not tighten it from the daily average.
|
|
1685
|
+
*/
|
|
1686
|
+
export declare const EDGE_ADMISSION_QUIET_AFTER_MS: number;
|
|
1687
|
+
/**
|
|
1688
|
+
* How old the sampler's own reading may be before it stops being evidence.
|
|
1689
|
+
*
|
|
1690
|
+
* Six hours, sized like `SIGNUP_CANARY_STALE_AFTER_MS` to a scheduler that
|
|
1691
|
+
* drops most of what it is asked for (AGL-2723) rather than to the cron
|
|
1692
|
+
* expression. Shorter than the quiet window on purpose: a stalled sampler
|
|
1693
|
+
* should be named as one BEFORE its frozen reading has had time to look like
|
|
1694
|
+
* an edge outage.
|
|
1695
|
+
*/
|
|
1696
|
+
export declare const EDGE_ADMISSION_SAMPLE_STALE_AFTER_MS: number;
|
|
1697
|
+
/**
|
|
1698
|
+
* Grade the last recorded metered-traffic sample.
|
|
1699
|
+
*
|
|
1700
|
+
* Pure, like every sibling. The sampler decides when the total last MOVED and
|
|
1701
|
+
* stamps it; this only asks how long ago that was. Keeping the movement
|
|
1702
|
+
* detection in the writer is what lets the door stay a read: a day rollover
|
|
1703
|
+
* resets the counter to zero, and a grader looking at one sample could not
|
|
1704
|
+
* tell that from traffic stopping.
|
|
1705
|
+
*/
|
|
1706
|
+
export declare function edgeAdmissionHealth(marker: EdgeAdmissionMarker | null, ms: number, nowMs?: number, quietAfterMs?: number, sampleStaleAfterMs?: number): EdgeAdmissionCheck;
|
|
1707
|
+
export interface AppCheckAttestationCheck extends HealthCheck {
|
|
1708
|
+
/** Allowed over total for the graded service, or null with no traffic. */
|
|
1709
|
+
allowRate: number | null;
|
|
1710
|
+
/** Requests the rate is taken over. */
|
|
1711
|
+
allowed: number;
|
|
1712
|
+
denied: number;
|
|
1713
|
+
/** Below this many requests the rate says nothing and is not graded. */
|
|
1714
|
+
minimumRequests: number;
|
|
1715
|
+
/** The floor the rate must clear. */
|
|
1716
|
+
minimumAllowRate: number;
|
|
1717
|
+
/** The service graded. Everything else is context. */
|
|
1718
|
+
service: string;
|
|
1719
|
+
/** Denials by `security`, so a red says which way it broke. */
|
|
1720
|
+
deniedBy: Record<string, number>;
|
|
1721
|
+
/** The window the counts cover. */
|
|
1722
|
+
windowMinutes: number;
|
|
1723
|
+
}
|
|
1724
|
+
/** The service signup depends on. See the docblock for why it is the only one. */
|
|
1725
|
+
export declare const APP_CHECK_GRADED_SERVICE = "identitytoolkit.googleapis.com";
|
|
1726
|
+
/**
|
|
1727
|
+
* Window the attestation rate is taken over.
|
|
1728
|
+
*
|
|
1729
|
+
* Six hours, not one. This metric is aggregated by Google on its own schedule
|
|
1730
|
+
* and arrives late, so a one-hour window reads empty for reasons that have
|
|
1731
|
+
* nothing to do with attestation. Six hours is long enough to always carry
|
|
1732
|
+
* traffic on this platform and short enough that a real collapse is caught the
|
|
1733
|
+
* same working day.
|
|
1734
|
+
*/
|
|
1735
|
+
export declare const APP_CHECK_WINDOW_MINUTES: number;
|
|
1736
|
+
/**
|
|
1737
|
+
* Requests below which the rate is not graded.
|
|
1738
|
+
*
|
|
1739
|
+
* A ratio over three requests is noise wearing a percentage. Twenty is a
|
|
1740
|
+
* fraction of the ~940 a day Identity Platform sees, so a healthy platform
|
|
1741
|
+
* always clears it, and a platform quiet enough not to is a platform where
|
|
1742
|
+
* this check has nothing to say.
|
|
1743
|
+
*/
|
|
1744
|
+
export declare const APP_CHECK_MIN_REQUESTS = 20;
|
|
1745
|
+
/**
|
|
1746
|
+
* The floor the allow rate must clear.
|
|
1747
|
+
*
|
|
1748
|
+
* 0.90, against a measured 0.997. The gap is deliberate: a handful of genuine
|
|
1749
|
+
* denials is ordinary — a stale tab, a bot, a browser that failed its
|
|
1750
|
+
* reCAPTCHA — and a floor set near the observed rate would page on those.
|
|
1751
|
+
*
|
|
1752
|
+
* What this is built to catch is not a drift of a few percent; it is the
|
|
1753
|
+
* documented failure mode of `recaptcha-allowlist.ts`, where an origin that is
|
|
1754
|
+
* attached and routed but not allowlisted renders a console that can never
|
|
1755
|
+
* sign anyone in. That reads as ~0%, not 85%.
|
|
1756
|
+
*/
|
|
1757
|
+
export declare const APP_CHECK_MIN_ALLOW_RATE = 0.9;
|
|
1758
|
+
/**
|
|
1759
|
+
* Grade attestation for real visitors.
|
|
1760
|
+
*
|
|
1761
|
+
* Pure, like every sibling: something else reads Cloud Monitoring, this
|
|
1762
|
+
* decides. Null samples are degraded rather than calm — an unreadable metric
|
|
1763
|
+
* is not evidence that attestation works, and this check exists precisely
|
|
1764
|
+
* because "we did not look" was being reported as "it is fine".
|
|
1765
|
+
*/
|
|
1766
|
+
export declare function appCheckAttestationHealth(samples: AppCheckVerificationSample[] | null, ms: number, service?: string, minimumRequests?: number, minimumAllowRate?: number, windowMinutes?: number): AppCheckAttestationCheck;
|