@aglyn/tenant-data-admin 1.0.0-beta.196 → 1.0.0-beta.198
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/consent-group-carry.d.ts +50 -0
- package/src/lib/server/consent-group-carry.js +319 -0
- package/src/lib/server/consent-group-carry.js.map +1 -0
- package/src/lib/server/consent-group-change.d.ts +246 -0
- package/src/lib/server/consent-group-change.js +1096 -0
- package/src/lib/server/consent-group-change.js.map +1 -0
- package/src/lib/server/dynamic-list-materialize.js +31 -5
- package/src/lib/server/dynamic-list-materialize.js.map +1 -1
- package/src/lib/server/erase.d.ts +21 -0
- package/src/lib/server/erase.js +32 -1
- package/src/lib/server/erase.js.map +1 -1
- package/src/lib/server/host-visitor-records.d.ts +7 -0
- package/src/lib/server/host-visitor-records.js +9 -3
- package/src/lib/server/host-visitor-records.js.map +1 -1
- package/src/lib/server/list-members.d.ts +23 -0
- package/src/lib/server/list-members.js +49 -7
- package/src/lib/server/list-members.js.map +1 -1
- package/src/lib/server/test-firestore-queries.d.ts +42 -0
- package/src/lib/server/test-firestore-queries.js +640 -0
- package/src/lib/server/test-firestore-queries.js.map +1 -0
- package/src/lib/server/upsert-contact.d.ts +15 -1
- package/src/lib/server/upsert-contact.js +8 -2
- package/src/lib/server/upsert-contact.js.map +1 -1
- package/src/tenancy.d.ts +2 -0
- package/src/tenancy.js +4 -0
- package/src/tenancy.js.map +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
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
|
+
* RUNNING A CONSENT GROUP CHANGE (AGL-3320) — the executor.
|
|
19
|
+
*
|
|
20
|
+
* `consent-group-change.ts` in `app-utils` decides WHAT a change requires;
|
|
21
|
+
* this carries it out, across as many invocations as it takes, without ever
|
|
22
|
+
* leaving a refusal unhonored in between.
|
|
23
|
+
*
|
|
24
|
+
* ## The order is the safety argument
|
|
25
|
+
*
|
|
26
|
+
* 1. START — one transaction on the org document: the stored declaration is
|
|
27
|
+
* the one the editor rendered, and no other change is in flight. It sets
|
|
28
|
+
* the marker `consentGroupsChange` and creates the job document.
|
|
29
|
+
* 2. CARRY — every carry of the plan through the three platform stores
|
|
30
|
+
* (`consent-group-carry.ts`), then every participant's carry phase.
|
|
31
|
+
* 3. CATCH UP — everything filed since the carry began, again.
|
|
32
|
+
* 4. DECLARE — one transaction: the marker still names this change in its
|
|
33
|
+
* carry phase and the stored declaration is still the one the job
|
|
34
|
+
* started from. Only then is the new declaration written.
|
|
35
|
+
* 5. CATCH UP — once more, for whatever raced the flip.
|
|
36
|
+
* 6. RE-HOME — every participant's holder records.
|
|
37
|
+
* 7. SWEEP — once the sweep delay has passed, the whole carry and every
|
|
38
|
+
* participant's sweep, idempotently.
|
|
39
|
+
* 8. DONE — the marker is cleared.
|
|
40
|
+
*
|
|
41
|
+
* The flip cannot happen before every carry and both catch-up passes are
|
|
42
|
+
* done, because the steps run strictly in order and `declare` is only ever
|
|
43
|
+
* reached from a finished `catch-up-pre`. Until the flip the declaration in
|
|
44
|
+
* force is the old one, so a change that stalls or is canceled before it
|
|
45
|
+
* has changed nothing anybody can observe — except refusals honored on more
|
|
46
|
+
* sites, which is the safe direction. A rename moves nothing, and runs the
|
|
47
|
+
* declare and finish alone.
|
|
48
|
+
*
|
|
49
|
+
* ## One invocation at a time, and any invocation may finish it
|
|
50
|
+
*
|
|
51
|
+
* The console's progress panel calls `continue` while it is open, and the
|
|
52
|
+
* cron backstop calls every fifteen minutes. Each claims a LEASE on the job
|
|
53
|
+
* by transaction and renews it on every unit it saves; a lease that lapses
|
|
54
|
+
* (a process that died) is simply claimed by the next caller. A unit is one
|
|
55
|
+
* page of one store, or one participant call, and every unit is idempotent,
|
|
56
|
+
* so a unit re-run after a crash writes nothing it already wrote.
|
|
57
|
+
*
|
|
58
|
+
* ## A failure is retried, and five in a row are shown
|
|
59
|
+
*
|
|
60
|
+
* A unit that throws releases the lease and counts a failure against the job;
|
|
61
|
+
* the next invocation retries it from its cursor. Five consecutive failures
|
|
62
|
+
* mark the job STALLED — the activity log says so once, and the panel shows
|
|
63
|
+
* the error with a retry — and the cron keeps trying. A participant's throw
|
|
64
|
+
* is one of these: the change never flips past a participant that could not
|
|
65
|
+
* carry its refusals (see `plugin-consent-group-change.ts`).
|
|
66
|
+
*/
|
|
67
|
+
import { type ConsentGroupChangeCounts, type ConsentGroupChangePlan, type ConsentGroupChangePreview, type ConsentGroupChangeStatus, type ConsentGroupChangeStep, type ConsentGroupChangeProgress, type ConsentGroupDeclaration, type ConsentGroupsApiInFlight, type ConsentGroupsApiRefusal, type ConsentGroupsApiStale, type ConsentGroupsChangePhase } from '@aglyn/aglyn/app-utils/consent-group-change';
|
|
68
|
+
import { logOrgActivity } from './organizations';
|
|
69
|
+
/** How long a claimed job stays claimed without a renewal. */
|
|
70
|
+
export declare const CONSENT_GROUP_CHANGE_LEASE_MS = 90000;
|
|
71
|
+
/** Consecutive failed units before a job is shown as stalled. */
|
|
72
|
+
export declare const CONSENT_GROUP_CHANGE_STALL_AFTER = 5;
|
|
73
|
+
/** Who asked, as the activity log records it. */
|
|
74
|
+
export interface ConsentGroupChangeActor {
|
|
75
|
+
uid: string | null;
|
|
76
|
+
email?: string | null;
|
|
77
|
+
}
|
|
78
|
+
/** Everything the executor reaches outside itself, injectable for the specs. */
|
|
79
|
+
export interface ConsentGroupChangeContext {
|
|
80
|
+
firestore?: FirebaseFirestore.Firestore;
|
|
81
|
+
log?: typeof logOrgActivity;
|
|
82
|
+
now?: () => number;
|
|
83
|
+
/** Whether a lockdown refuses the cron acting for this org and actor. */
|
|
84
|
+
lockedOut?: (org: Record<string, unknown>, uid: string | null) => Promise<boolean>;
|
|
85
|
+
}
|
|
86
|
+
/** Where a unit of the current step stands. */
|
|
87
|
+
interface UnitState {
|
|
88
|
+
cursor: string | null;
|
|
89
|
+
done: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** The job document, `orgs/{orgId}/consentGroupChanges/{changeId}`. */
|
|
92
|
+
export interface ConsentGroupChangeJob {
|
|
93
|
+
changeId: string;
|
|
94
|
+
orgId: string;
|
|
95
|
+
status: ConsentGroupChangeStatus;
|
|
96
|
+
step: ConsentGroupChangeStep;
|
|
97
|
+
/** The marker's phase while running; `done` or `canceled` after. */
|
|
98
|
+
phase: ConsentGroupsChangePhase | 'done' | 'canceled';
|
|
99
|
+
/** The steps this change runs, in order. */
|
|
100
|
+
steps: ConsentGroupChangeStep[];
|
|
101
|
+
/** The stored declaration it started from: what the flip re-checks. */
|
|
102
|
+
beforeFingerprint: string;
|
|
103
|
+
before: ConsentGroupDeclaration;
|
|
104
|
+
after: ConsentGroupDeclaration;
|
|
105
|
+
plan: Omit<ConsentGroupChangePlan, 'before' | 'after'>;
|
|
106
|
+
/** The plan's lines as the activity log writes them. */
|
|
107
|
+
lines: string[];
|
|
108
|
+
siteNames: Record<string, string>;
|
|
109
|
+
/** The participants registered when it started — every one of them runs. */
|
|
110
|
+
participants: string[];
|
|
111
|
+
/** The current step's units, by key. */
|
|
112
|
+
units: Record<string, UnitState>;
|
|
113
|
+
counts: ConsentGroupChangeCounts;
|
|
114
|
+
sitesReceiving: string[];
|
|
115
|
+
plugins: Record<string, Record<string, number>>;
|
|
116
|
+
lease: {
|
|
117
|
+
owner: string;
|
|
118
|
+
untilMs: number;
|
|
119
|
+
} | null;
|
|
120
|
+
failures: number;
|
|
121
|
+
stalled: boolean;
|
|
122
|
+
lastError: string | null;
|
|
123
|
+
actor: {
|
|
124
|
+
uid: string | null;
|
|
125
|
+
email: string | null;
|
|
126
|
+
};
|
|
127
|
+
startedAtMs: number;
|
|
128
|
+
carryStartedAtMs: number;
|
|
129
|
+
declaredAtMs: number | null;
|
|
130
|
+
finishedAtMs: number | null;
|
|
131
|
+
canceledAtMs: number | null;
|
|
132
|
+
}
|
|
133
|
+
/** What the route answers for a job. */
|
|
134
|
+
export interface ConsentGroupChangeStatusBody {
|
|
135
|
+
changeId: string;
|
|
136
|
+
phase: ConsentGroupsChangePhase | 'done' | 'canceled';
|
|
137
|
+
done: boolean;
|
|
138
|
+
progress: ConsentGroupChangeProgress;
|
|
139
|
+
}
|
|
140
|
+
/** A refusal the route turns into its status code and body. */
|
|
141
|
+
export type ConsentGroupChangeRefusal = {
|
|
142
|
+
ok: false;
|
|
143
|
+
status: 400;
|
|
144
|
+
body: ConsentGroupsApiRefusal;
|
|
145
|
+
} | {
|
|
146
|
+
ok: false;
|
|
147
|
+
status: 404;
|
|
148
|
+
body: {
|
|
149
|
+
error: string;
|
|
150
|
+
};
|
|
151
|
+
} | {
|
|
152
|
+
ok: false;
|
|
153
|
+
status: 409;
|
|
154
|
+
body: ConsentGroupsApiStale | ConsentGroupsApiInFlight;
|
|
155
|
+
};
|
|
156
|
+
export declare function consentGroupChangeStatus(job: ConsentGroupChangeJob, nowMs?: number): ConsentGroupChangeStatusBody;
|
|
157
|
+
/**
|
|
158
|
+
* What a change would do, counted and worded, writing nothing.
|
|
159
|
+
*
|
|
160
|
+
* Refused exactly as `apply` would be — a change in flight, a stale
|
|
161
|
+
* `expected`, an invalid declaration — so the review step never shows a
|
|
162
|
+
* change the next click cannot make.
|
|
163
|
+
*/
|
|
164
|
+
export declare function previewConsentGroupChange(input: {
|
|
165
|
+
orgId: string;
|
|
166
|
+
expected: unknown;
|
|
167
|
+
groups: unknown;
|
|
168
|
+
/** The org document, when the caller already read it. */
|
|
169
|
+
org?: Record<string, unknown> | null;
|
|
170
|
+
} & ConsentGroupChangeContext): Promise<{
|
|
171
|
+
ok: true;
|
|
172
|
+
preview: ConsentGroupChangePreview;
|
|
173
|
+
} | ConsentGroupChangeRefusal>;
|
|
174
|
+
/**
|
|
175
|
+
* Starts a change: the org marker and the job, in one transaction that
|
|
176
|
+
* refuses a change in flight, a stale `expected` and an invalid declaration.
|
|
177
|
+
* Writes nothing else — the caller advances it.
|
|
178
|
+
*/
|
|
179
|
+
export declare function startConsentGroupChange(input: {
|
|
180
|
+
orgId: string;
|
|
181
|
+
actor: ConsentGroupChangeActor;
|
|
182
|
+
expected: unknown;
|
|
183
|
+
groups: unknown;
|
|
184
|
+
} & ConsentGroupChangeContext): Promise<{
|
|
185
|
+
ok: true;
|
|
186
|
+
changeId: string;
|
|
187
|
+
job: ConsentGroupChangeJob;
|
|
188
|
+
} | ConsentGroupChangeRefusal>;
|
|
189
|
+
/**
|
|
190
|
+
* Advances a change until it finishes, waits for its sweep, or reaches
|
|
191
|
+
* `deadlineMs`. Answers the job's status either way — including when another
|
|
192
|
+
* caller holds it, which is not a failure.
|
|
193
|
+
*/
|
|
194
|
+
export declare function advanceConsentGroupChange(input: {
|
|
195
|
+
orgId: string;
|
|
196
|
+
changeId: string;
|
|
197
|
+
deadlineMs: number;
|
|
198
|
+
/** Who holds the lease; one per invocation. */
|
|
199
|
+
owner?: string;
|
|
200
|
+
} & ConsentGroupChangeContext): Promise<{
|
|
201
|
+
ok: true;
|
|
202
|
+
status: ConsentGroupChangeStatusBody;
|
|
203
|
+
} | ConsentGroupChangeRefusal>;
|
|
204
|
+
/**
|
|
205
|
+
* Stops a change before it takes effect: clears the marker and leaves every
|
|
206
|
+
* refusal already carried, which only ever honors an opt-out on more sites.
|
|
207
|
+
* Refused once the declaration has flipped — there is nothing left to stop
|
|
208
|
+
* before, and the only way back is another change.
|
|
209
|
+
*/
|
|
210
|
+
export declare function cancelConsentGroupChange(input: {
|
|
211
|
+
orgId: string;
|
|
212
|
+
changeId: string;
|
|
213
|
+
actor: ConsentGroupChangeActor;
|
|
214
|
+
} & ConsentGroupChangeContext): Promise<{
|
|
215
|
+
ok: true;
|
|
216
|
+
status: ConsentGroupChangeStatusBody;
|
|
217
|
+
} | ConsentGroupChangeRefusal>;
|
|
218
|
+
/** A job's status, or `null` for a change the org never had. */
|
|
219
|
+
export declare function readConsentGroupChangeStatus(input: {
|
|
220
|
+
orgId: string;
|
|
221
|
+
changeId: string;
|
|
222
|
+
} & ConsentGroupChangeContext): Promise<ConsentGroupChangeStatusBody | null>;
|
|
223
|
+
export interface DueConsentGroupChangeReport {
|
|
224
|
+
orgId: string;
|
|
225
|
+
changeId: string;
|
|
226
|
+
phase: ConsentGroupsChangePhase;
|
|
227
|
+
/** What happened on this run. */
|
|
228
|
+
outcome: 'advanced' | 'listed' | 'locked' | 'orphaned' | 'skipped';
|
|
229
|
+
status?: ConsentGroupChangeStatusBody;
|
|
230
|
+
error?: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Advances every change in flight, oldest org id first, until `deadlineMs`.
|
|
234
|
+
*
|
|
235
|
+
* A change nobody is watching still finishes: the progress panel is a
|
|
236
|
+
* convenience, and this is the guarantee. A lockdown on the org — or on the
|
|
237
|
+
* member who started the change — pauses it, like every other write the
|
|
238
|
+
* workspace makes. A marker whose job document is gone is a change nothing
|
|
239
|
+
* can finish, and is cleared rather than blocking every change after it.
|
|
240
|
+
*/
|
|
241
|
+
export declare function advanceDueConsentGroupChanges(input: {
|
|
242
|
+
deadlineMs: number;
|
|
243
|
+
dryRun?: boolean;
|
|
244
|
+
limit?: number;
|
|
245
|
+
} & ConsentGroupChangeContext): Promise<DueConsentGroupChangeReport[]>;
|
|
246
|
+
export {};
|