@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.
@@ -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
+ }>;