@aglyn/tenant-data-admin 1.0.0-beta.205 → 1.0.0-beta.207
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/package.json +7 -7
- package/src/lib/server/connect-payout-failure.js +18 -3
- package/src/lib/server/connect-payout-failure.js.map +1 -1
- package/src/lib/server/email-deliverability.js +19 -1
- package/src/lib/server/email-deliverability.js.map +1 -1
- package/src/lib/server/email-send-rate.js +32 -1
- package/src/lib/server/email-send-rate.js.map +1 -1
- package/src/lib/server/host-memberships.d.ts +20 -0
- package/src/lib/server/host-memberships.js +29 -1
- package/src/lib/server/host-memberships.js.map +1 -1
- package/src/lib/server/notifications.d.ts +13 -0
- package/src/lib/server/notifications.js +35 -23
- package/src/lib/server/notifications.js.map +1 -1
- package/src/lib/server/operator-alerts.d.ts +215 -0
- package/src/lib/server/operator-alerts.js +683 -0
- package/src/lib/server/operator-alerts.js.map +1 -0
- package/src/lib/server/operator-health.d.ts +59 -0
- package/src/lib/server/operator-health.js +200 -0
- package/src/lib/server/operator-health.js.map +1 -0
- package/src/lib/server/staff-alert-email.d.ts +18 -5
- package/src/lib/server/staff-alert-email.js +36 -5
- package/src/lib/server/staff-alert-email.js.map +1 -1
- package/src/lib/server/transfer-host.d.ts +52 -0
- package/src/lib/server/transfer-host.js +377 -0
- package/src/lib/server/transfer-host.js.map +1 -0
- package/src/tenancy.d.ts +2 -0
- package/src/tenancy.js +4 -0
- package/src/tenancy.js.map +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
import { type AglynNotificationType, type OperatorAlertContext, type OperatorAlertDefinition, type OperatorAlertSettings } from '@aglyn/aglyn/server';
|
|
18
|
+
import { type SendEmailResult } from '@aglyn/shared-util-email';
|
|
19
|
+
/**
|
|
20
|
+
* RAISING AN OPERATOR ALERT (AGL-3377): the one server entry point every
|
|
21
|
+
* alert in the registry goes through.
|
|
22
|
+
*
|
|
23
|
+
* In order, for one call:
|
|
24
|
+
*
|
|
25
|
+
* 1. The type is looked up in the registry (core's catalog plus what plugins
|
|
26
|
+
* registered). A plugin may pass its definition itself, which registers
|
|
27
|
+
* it in this process if its declarations have not loaded yet.
|
|
28
|
+
* 2. A `dedupeKey` is claimed against `operatorAlertState`, so a condition
|
|
29
|
+
* that flaps inside the type's window is told once, and a type with
|
|
30
|
+
* `minOccurrences` fires only once it has recurred that often.
|
|
31
|
+
* 3. The staff console notification is written for every staff member, as
|
|
32
|
+
* `notifyStaff` always has.
|
|
33
|
+
* 4. Staff's switches (`platformSettings/operatorAlerts`, coded defaults
|
|
34
|
+
* until changed) decide the rest: off is console only, `digest` queues it
|
|
35
|
+
* for the once-a-day digest, `immediate` emails the operator now.
|
|
36
|
+
* 5. The email is the `operator-alert` system email, to
|
|
37
|
+
* `resolveStaffAlertRecipients()`; the optional out-of-band webhook
|
|
38
|
+
* (`OPERATOR_ALERT_WEBHOOK_URL`) gets the same alert beside it, so a mail
|
|
39
|
+
* provider that is itself the outage still reaches somebody.
|
|
40
|
+
*
|
|
41
|
+
* Never throws: an alert is raised from inside the failure it reports, and a
|
|
42
|
+
* second failure there must not replace the first.
|
|
43
|
+
*/
|
|
44
|
+
/** Where staff's per-type switches live. */
|
|
45
|
+
export declare const OPERATOR_ALERT_SETTINGS_COLLECTION = "platformSettings";
|
|
46
|
+
export declare const OPERATOR_ALERT_SETTINGS_DOC = "operatorAlerts";
|
|
47
|
+
/** One document per (type, dedupeKey): when it last fired, how often since. */
|
|
48
|
+
export declare const OPERATOR_ALERT_STATE_COLLECTION = "operatorAlertState";
|
|
49
|
+
/** Alerts waiting for the daily digest. */
|
|
50
|
+
export declare const OPERATOR_ALERT_DIGEST_COLLECTION = "operatorAlertDigest";
|
|
51
|
+
/** The hour, UTC, the digest goes out when staff have not chosen one. */
|
|
52
|
+
export declare const OPERATOR_ALERT_DIGEST_DEFAULT_HOUR_UTC = 14;
|
|
53
|
+
export interface OperatorAlertSettingsDoc extends OperatorAlertSettings {
|
|
54
|
+
/** The hour of the day, UTC, the digest is sent. */
|
|
55
|
+
digestHourUtc?: number;
|
|
56
|
+
updatedAtMs?: number;
|
|
57
|
+
updatedByEmail?: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface RaiseOperatorAlertOptions {
|
|
60
|
+
/**
|
|
61
|
+
* What makes two raises the SAME alert — a dispute id, an org and month, a
|
|
62
|
+
* check id. Absent never dedupes: every raise is told.
|
|
63
|
+
*/
|
|
64
|
+
dedupeKey?: string;
|
|
65
|
+
/** Values for the definition's `{{tokens}}`. Never personal data. */
|
|
66
|
+
context?: OperatorAlertContext;
|
|
67
|
+
/** A console path or absolute URL; overrides the definition's link. */
|
|
68
|
+
url?: string;
|
|
69
|
+
/** The title as written by the caller; overrides the rendered one. */
|
|
70
|
+
subject?: string;
|
|
71
|
+
/** The body as written by the caller; overrides the rendered one. */
|
|
72
|
+
body?: string;
|
|
73
|
+
orgId?: string;
|
|
74
|
+
hostId?: string;
|
|
75
|
+
}
|
|
76
|
+
export type OperatorAlertOutcome = 'deduped' | 'below-threshold' | 'console-only' | 'queued' | 'delivered' | 'undelivered';
|
|
77
|
+
export interface OperatorAlertResult {
|
|
78
|
+
outcome: OperatorAlertOutcome;
|
|
79
|
+
type: string;
|
|
80
|
+
email?: SendEmailResult;
|
|
81
|
+
webhook?: OperatorAlertWebhookResult;
|
|
82
|
+
}
|
|
83
|
+
export type OperatorAlertWebhookResult = {
|
|
84
|
+
posted: true;
|
|
85
|
+
status: number;
|
|
86
|
+
} | {
|
|
87
|
+
posted: false;
|
|
88
|
+
reason: 'unconfigured' | 'refused' | 'network';
|
|
89
|
+
status?: number;
|
|
90
|
+
};
|
|
91
|
+
/** One alert, rendered, as it is delivered and queued. */
|
|
92
|
+
export interface RenderedOperatorAlert {
|
|
93
|
+
type: string;
|
|
94
|
+
label: string;
|
|
95
|
+
tier: OperatorAlertDefinition['tier'];
|
|
96
|
+
category: OperatorAlertDefinition['category'];
|
|
97
|
+
title: string;
|
|
98
|
+
body: string;
|
|
99
|
+
/** Console path or absolute URL; `''` when there is none. */
|
|
100
|
+
link: string;
|
|
101
|
+
notificationType: AglynNotificationType;
|
|
102
|
+
orgId?: string;
|
|
103
|
+
hostId?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Drop the in-process settings cache; the console calls it after a write. */
|
|
106
|
+
export declare function invalidateOperatorAlertSettingsCache(): void;
|
|
107
|
+
/** Clamps a stored settings document to what the pipeline understands. */
|
|
108
|
+
export declare function normalizeOperatorAlertSettings(raw: unknown): OperatorAlertSettingsDoc;
|
|
109
|
+
/**
|
|
110
|
+
* Staff's switches. An unreadable document is the coded defaults, uncached:
|
|
111
|
+
* an alert must still go out when the settings cannot be read.
|
|
112
|
+
*/
|
|
113
|
+
export declare function readOperatorAlertSettings(): Promise<OperatorAlertSettingsDoc>;
|
|
114
|
+
export type OperatorAlertClaim = {
|
|
115
|
+
fire: true;
|
|
116
|
+
repeats: number;
|
|
117
|
+
} | {
|
|
118
|
+
fire: false;
|
|
119
|
+
reason: 'deduped' | 'below-threshold';
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Whether a raise with this key fires now. One transaction on the key's
|
|
123
|
+
* state document: inside the window after a fire it is counted and
|
|
124
|
+
* suppressed; before it reaches `minOccurrences` it is counted and held.
|
|
125
|
+
* A store that cannot be read FIRES — a duplicate beats a silence.
|
|
126
|
+
*/
|
|
127
|
+
export declare function claimOperatorAlert(definition: Pick<OperatorAlertDefinition, 'type' | 'dedupeWindowMinutes' | 'minOccurrences'>, dedupeKey: string, now?: number): Promise<OperatorAlertClaim>;
|
|
128
|
+
/** The webhook URL, when one is configured and is http(s). */
|
|
129
|
+
export declare function operatorAlertWebhookUrl(): string;
|
|
130
|
+
/**
|
|
131
|
+
* The JSON the webhook receives. `text` is what a Slack-compatible incoming
|
|
132
|
+
* webhook renders on its own; `alert` carries the fields for anything that
|
|
133
|
+
* wants to route on them.
|
|
134
|
+
*/
|
|
135
|
+
export declare function operatorAlertWebhookPayload(alerts: readonly RenderedOperatorAlert[], options?: {
|
|
136
|
+
digest?: boolean;
|
|
137
|
+
now?: number;
|
|
138
|
+
}): Record<string, unknown>;
|
|
139
|
+
/**
|
|
140
|
+
* Posts alerts to `OPERATOR_ALERT_WEBHOOK_URL`, signed when
|
|
141
|
+
* `OPERATOR_ALERT_WEBHOOK_SECRET` is set: `x-operator-alert-timestamp` is the
|
|
142
|
+
* Unix time and `x-operator-alert-signature` is `sha256=` plus the hex HMAC
|
|
143
|
+
* of `{timestamp}.{body}`. Unset means email only. Never throws.
|
|
144
|
+
*/
|
|
145
|
+
export declare function postOperatorAlertWebhook(alerts: readonly RenderedOperatorAlert[], options?: {
|
|
146
|
+
digest?: boolean;
|
|
147
|
+
now?: number;
|
|
148
|
+
}): Promise<OperatorAlertWebhookResult>;
|
|
149
|
+
/** A console path as an inbox or webhook can follow it, or `''`. */
|
|
150
|
+
export declare function operatorAlertAbsoluteLink(link: string | undefined | null): string;
|
|
151
|
+
/** Resolves the definition a call names, registering a plugin's own. */
|
|
152
|
+
export declare function resolveOperatorAlertDefinition(type: string | OperatorAlertDefinition): OperatorAlertDefinition;
|
|
153
|
+
export declare function renderOperatorAlert(definition: OperatorAlertDefinition, options?: RaiseOperatorAlertOptions): RenderedOperatorAlert;
|
|
154
|
+
/** See the module comment. Never throws. */
|
|
155
|
+
export declare function raiseOperatorAlert(type: string | OperatorAlertDefinition, options?: RaiseOperatorAlertOptions): Promise<OperatorAlertResult>;
|
|
156
|
+
/** At most this many alerts are listed in one digest; the rest are counted. */
|
|
157
|
+
export declare const OPERATOR_DIGEST_MAX_LISTED = 50;
|
|
158
|
+
export interface OperatorDigestResult {
|
|
159
|
+
/** Whether a digest went out (or, on a dry run, would). */
|
|
160
|
+
sent: boolean;
|
|
161
|
+
reason?: 'not-due' | 'already-sent' | 'empty' | 'dry-run' | 'unavailable';
|
|
162
|
+
count: number;
|
|
163
|
+
email?: SendEmailResult;
|
|
164
|
+
webhook?: OperatorAlertWebhookResult;
|
|
165
|
+
}
|
|
166
|
+
/** The state document that claims one UTC day's digest. */
|
|
167
|
+
export declare function operatorDigestMarkerId(now: number): string;
|
|
168
|
+
/** The digest's body: one paragraph per alert, newest last. */
|
|
169
|
+
export declare function composeOperatorDigestBody(alerts: readonly RenderedOperatorAlert[], total?: number): string;
|
|
170
|
+
/**
|
|
171
|
+
* Sends the day's digest once the configured hour has passed: every queued
|
|
172
|
+
* alert in one `operator-alert-digest` email and one webhook post, then
|
|
173
|
+
* clears what it sent. Once per UTC day, claimed by creating
|
|
174
|
+
* `operatorAlertState/digest-{day}`, so two ticks never both send it.
|
|
175
|
+
*/
|
|
176
|
+
export declare function sendOperatorAlertDigest(options?: {
|
|
177
|
+
now?: number;
|
|
178
|
+
dryRun?: boolean;
|
|
179
|
+
force?: boolean;
|
|
180
|
+
}): Promise<OperatorDigestResult>;
|
|
181
|
+
/** One registry entry as Staff → Operator alerts lists it. */
|
|
182
|
+
export interface OperatorAlertCatalogRow {
|
|
183
|
+
type: string;
|
|
184
|
+
label: string;
|
|
185
|
+
description: string;
|
|
186
|
+
tier: OperatorAlertDefinition['tier'];
|
|
187
|
+
category: OperatorAlertDefinition['category'];
|
|
188
|
+
pluginId: string | null;
|
|
189
|
+
defaults: {
|
|
190
|
+
enabled: boolean;
|
|
191
|
+
delivery: OperatorAlertDefinition['delivery'];
|
|
192
|
+
};
|
|
193
|
+
effective: {
|
|
194
|
+
enabled: boolean;
|
|
195
|
+
delivery: OperatorAlertDefinition['delivery'];
|
|
196
|
+
};
|
|
197
|
+
/** Whether staff have answered for this type, rather than the default. */
|
|
198
|
+
overridden: boolean;
|
|
199
|
+
}
|
|
200
|
+
/** Every registered type with its default and its effective setting. */
|
|
201
|
+
export declare function operatorAlertCatalogRows(definitions: readonly OperatorAlertDefinition[], settings: OperatorAlertSettingsDoc): OperatorAlertCatalogRow[];
|
|
202
|
+
/**
|
|
203
|
+
* Sends one `[Test]` alert of a type straight to the operator's inbox and
|
|
204
|
+
* webhook, past dedupe and staff's switches, so an operator can prove the
|
|
205
|
+
* channels reach them. Writes no console notification.
|
|
206
|
+
*/
|
|
207
|
+
export declare function sendOperatorAlertTest(definition: OperatorAlertDefinition): Promise<{
|
|
208
|
+
email: SendEmailResult;
|
|
209
|
+
webhook: OperatorAlertWebhookResult;
|
|
210
|
+
}>;
|
|
211
|
+
/** Where operator mail goes, named but never listed, for the staff page. */
|
|
212
|
+
export declare function describeOperatorAlertRecipients(): Promise<{
|
|
213
|
+
source: 'STAFF_ALERT_EMAIL' | 'NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL' | 'staff-accounts' | 'none';
|
|
214
|
+
count: number;
|
|
215
|
+
}>;
|