@serve.zone/dcrouter 17.10.2 → 18.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +360 -360
  3. package/dist_ts/00_commitinfo_data.js +2 -2
  4. package/dist_ts/acme/acme-failure-classification.d.ts +64 -0
  5. package/dist_ts/acme/acme-failure-classification.js +114 -0
  6. package/dist_ts/acme/classes.smartacme-lifecycle.d.ts +42 -0
  7. package/dist_ts/acme/classes.smartacme-lifecycle.js +75 -4
  8. package/dist_ts/acme/index.d.ts +1 -0
  9. package/dist_ts/acme/index.js +2 -1
  10. package/dist_ts/classes.dcrouter.d.ts +33 -9
  11. package/dist_ts/classes.dcrouter.js +145 -11
  12. package/dist_ts/config/classes.route-config-manager.d.ts +56 -0
  13. package/dist_ts/config/classes.route-config-manager.js +161 -1
  14. package/dist_ts/db/documents/classes.dns-authority.doc.d.ts +30 -0
  15. package/dist_ts/db/documents/classes.dns-authority.doc.js +108 -0
  16. package/dist_ts/db/documents/index.d.ts +1 -0
  17. package/dist_ts/db/documents/index.js +2 -1
  18. package/dist_ts/dns/classes.dns-server-runtime.d.ts +109 -1
  19. package/dist_ts/dns/classes.dns-server-runtime.js +212 -34
  20. package/dist_ts/dns/domain-ownership.d.ts +111 -0
  21. package/dist_ts/dns/domain-ownership.js +152 -0
  22. package/dist_ts/dns/index.d.ts +2 -0
  23. package/dist_ts/dns/index.js +3 -1
  24. package/dist_ts/dns/manager.dns-authority.d.ts +143 -0
  25. package/dist_ts/dns/manager.dns-authority.js +481 -0
  26. package/dist_ts/dns/manager.dns.d.ts +121 -12
  27. package/dist_ts/dns/manager.dns.js +298 -28
  28. package/dist_ts/email/classes.accepted-email-spool.d.ts +15 -0
  29. package/dist_ts/email/classes.accepted-email-spool.js +102 -23
  30. package/dist_ts/email/classes.smartmta-blob-storage-manager.js +12 -3
  31. package/dist_ts/errors/error.codes.d.ts +4 -0
  32. package/dist_ts/errors/error.codes.js +5 -1
  33. package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
  34. package/dist_ts/opsserver/classes.opsserver.js +3 -1
  35. package/dist_ts/opsserver/handlers/acme-config.handler.js +6 -1
  36. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +16 -0
  37. package/dist_ts/opsserver/handlers/certificate.handler.js +96 -10
  38. package/dist_ts/opsserver/handlers/config.handler.js +4 -2
  39. package/dist_ts/opsserver/handlers/dns-authority.handler.d.ts +20 -0
  40. package/dist_ts/opsserver/handlers/dns-authority.handler.js +108 -0
  41. package/dist_ts/opsserver/handlers/dns-provider.handler.js +5 -1
  42. package/dist_ts/opsserver/handlers/domain.handler.js +9 -1
  43. package/dist_ts/opsserver/handlers/gatewayclient.handler.js +2 -2
  44. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  45. package/dist_ts/opsserver/handlers/index.js +2 -1
  46. package/dist_ts_interfaces/data/dns-authority.d.ts +98 -0
  47. package/dist_ts_interfaces/data/dns-authority.js +26 -0
  48. package/dist_ts_interfaces/data/index.d.ts +1 -0
  49. package/dist_ts_interfaces/data/index.js +2 -1
  50. package/dist_ts_interfaces/data/route-management.d.ts +9 -2
  51. package/dist_ts_interfaces/data/route-management.js +3 -1
  52. package/dist_ts_interfaces/requests/certificate.d.ts +23 -0
  53. package/dist_ts_interfaces/requests/certificate.js +1 -1
  54. package/dist_ts_interfaces/requests/dns-authority.d.ts +80 -0
  55. package/dist_ts_interfaces/requests/dns-authority.js +3 -0
  56. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  57. package/dist_ts_interfaces/requests/index.js +2 -1
  58. package/dist_ts_oci_container/index.js +9 -4
  59. package/dist_ts_web/00_commitinfo_data.js +2 -2
  60. package/package.json +1 -1
  61. package/readme.hints.md +412 -0
  62. package/readme.md +48 -6
  63. package/ts/00_commitinfo_data.ts +1 -1
  64. package/ts/acme/acme-failure-classification.ts +201 -0
  65. package/ts/acme/classes.smartacme-lifecycle.ts +104 -3
  66. package/ts/acme/index.ts +1 -0
  67. package/ts/classes.dcrouter.ts +193 -23
  68. package/ts/config/classes.route-config-manager.ts +197 -0
  69. package/ts/db/documents/classes.dns-authority.doc.ts +49 -0
  70. package/ts/db/documents/index.ts +1 -0
  71. package/ts/dns/classes.dns-server-runtime.ts +257 -38
  72. package/ts/dns/domain-ownership.ts +272 -0
  73. package/ts/dns/index.ts +2 -0
  74. package/ts/dns/manager.dns-authority.ts +558 -0
  75. package/ts/dns/manager.dns.ts +373 -27
  76. package/ts/email/classes.accepted-email-spool.ts +98 -21
  77. package/ts/email/classes.smartmta-blob-storage-manager.ts +10 -2
  78. package/ts/errors/error.codes.ts +4 -0
  79. package/ts/opsserver/classes.opsserver.ts +2 -0
  80. package/ts/opsserver/handlers/acme-config.handler.ts +7 -0
  81. package/ts/opsserver/handlers/certificate.handler.ts +103 -8
  82. package/ts/opsserver/handlers/config.handler.ts +3 -1
  83. package/ts/opsserver/handlers/dns-authority.handler.ts +142 -0
  84. package/ts/opsserver/handlers/dns-provider.handler.ts +6 -0
  85. package/ts/opsserver/handlers/domain.handler.ts +12 -0
  86. package/ts/opsserver/handlers/gatewayclient.handler.ts +1 -1
  87. package/ts/opsserver/handlers/index.ts +1 -0
  88. package/ts/readme.md +1 -1
  89. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -0,0 +1,481 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ import { DnsAuthorityDoc, DomainDoc } from '../db/documents/index.js';
4
+ const normalizeZone = (zoneArg) => zoneArg.trim().toLowerCase().replace(/\.$/, '');
5
+ /** Strip the trailing dot smartdns/DoH returns on NS values. */
6
+ const normalizeNameserver = (nameserverArg) => nameserverArg.trim().toLowerCase().replace(/\.$/, '');
7
+ const isUsableZoneName = (zoneArg) => {
8
+ if (!zoneArg || zoneArg.length > 253)
9
+ return false;
10
+ if (zoneArg.includes('*') || zoneArg.includes(' ') || zoneArg.includes(','))
11
+ return false;
12
+ const labels = zoneArg.split('.');
13
+ if (labels.length < 2)
14
+ return false;
15
+ return labels.every((labelArg) => Boolean(labelArg)
16
+ && labelArg.length <= 63
17
+ && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(labelArg));
18
+ };
19
+ /**
20
+ * DnsAuthorityManager — owns which zones dcrouter may answer for
21
+ * authoritatively, and proves that claim rather than accepting it.
22
+ *
23
+ * Why this exists: `dnsScopes` was the last un-migrated bootstrap option in an
24
+ * otherwise DB-driven router. It was read once at startup and had no mutation
25
+ * path, so claiming a newly delegated zone required a restart — measured at
26
+ * 30–60 s of total public outage. But simply making the declared list editable
27
+ * through the ops API would have been worse than the disease: the domain
28
+ * ownership predicate accepts zone coverage as *proof* precisely because a
29
+ * caller cannot write it. A `setDnsScopes` mutation would let an operator append
30
+ * any domain and manufacture that proof — the identical self-assertion vector
31
+ * that `createDcrouterDomain` used to have.
32
+ *
33
+ * So a zone earns authority by delegation instead: its public NS records must
34
+ * name our `dnsNsDomains`. An ops-API caller cannot fake that without actually
35
+ * controlling the domain, so the proof stays unforgeable while becoming
36
+ * mutable at runtime. It is also the comparison that was missing — claimed
37
+ * authority versus real delegation, checked by the mechanism itself rather than
38
+ * by an audit bolted on afterwards.
39
+ *
40
+ * **There is exactly one source of authority: this database document.** An
41
+ * earlier revision kept bootstrap `dnsScopes` as an always-in-effect floor and
42
+ * unioned it with the verified set. That is gone. A floor is a second
43
+ * representation of the same fact, it can only be changed by a redeploy, and it
44
+ * cannot be revoked through the API — all three of which are the properties
45
+ * that produced the outage this path exists to prevent.
46
+ *
47
+ * Removing the floor makes the *unreadable database* case load-bearing, so it is
48
+ * handled explicitly rather than by accident. Three situations, three answers:
49
+ *
50
+ * - **document missing** — a readable database that has never been seeded. The
51
+ * authority set is known, and it is empty: claim nothing. Logged at `error`
52
+ * with the remediation, and the startup drift audit enumerates every zone
53
+ * delegated to us that we are refusing to serve, which is the worklist.
54
+ * - **document present, no zones** — identical handling, different message:
55
+ * somebody revoked everything, which is a legitimate state we must not
56
+ * silently repopulate.
57
+ * - **document unreadable** — the authority set is *unknown*. `start()` throws
58
+ * rather than reporting an empty set, because rendering an unknown as "claim
59
+ * nothing" is exactly how a transient database fault would take every zone
60
+ * off the air. `DnsServerRuntime.setup()` then refuses to start on
61
+ * `getState() === 'unavailable'`. That second check is not redundant:
62
+ * `DnsServer.dependsOn('DnsManager')` only *orders* startup, and taskbuffer
63
+ * starts later levels even when an earlier optional service failed, so the
64
+ * consumer has to read the state itself. Both services stay failed and
65
+ * dcrouter runs degraded until it is restarted with a readable database —
66
+ * DNS down and visibly failed, rather than up and asserting nothing.
67
+ *
68
+ * Fail closed on authority, never silently on service: with an empty set the
69
+ * DNS server still starts, still serves DoH, and still picks zones up the moment
70
+ * one is verified — without a restart.
71
+ */
72
+ export class DnsAuthorityManager {
73
+ getExpectedNameservers;
74
+ verifiedZones = [];
75
+ updatedAt = 0;
76
+ updatedBy = '';
77
+ state = 'unavailable';
78
+ reconciler;
79
+ constructor(getExpectedNameservers) {
80
+ this.getExpectedNameservers = getExpectedNameservers;
81
+ }
82
+ // ==========================================================================
83
+ // Lifecycle
84
+ // ==========================================================================
85
+ async start() {
86
+ let doc;
87
+ try {
88
+ doc = await DnsAuthorityDoc.load();
89
+ }
90
+ catch (error) {
91
+ // An unknown authority set is not an empty one. Refuse to start rather
92
+ // than let every consumer read "no zones" as "revoke everything".
93
+ this.state = 'unavailable';
94
+ this.verifiedZones = [];
95
+ throw new Error(`DnsAuthorityManager: the stored DNS authority set is unreadable (${error.message}). `
96
+ + 'dcrouter serves DNS authority from the database alone, so an unreadable document leaves authority '
97
+ + 'unknown — refusing to start rather than claiming nothing for every zone.');
98
+ }
99
+ this.state = 'loaded';
100
+ this.verifiedZones = doc?.verifiedZones ? [...doc.verifiedZones] : [];
101
+ this.updatedAt = doc?.updatedAt ?? 0;
102
+ this.updatedBy = doc?.updatedBy ?? '';
103
+ if (this.verifiedZones.length > 0) {
104
+ logger.log('info', `DnsAuthorityManager: ${this.verifiedZones.length} delegation-verified zone(s) loaded `
105
+ + `(${this.verifiedZones.map((entry) => normalizeZone(entry.zone)).join(', ')})`, { zone: 'dns' });
106
+ return;
107
+ }
108
+ // Known-empty. Loud, because every zone is unserved until something is
109
+ // verified, and because this is the state a first deploy lands in.
110
+ logger.log('error', doc
111
+ ? 'DnsAuthorityManager: the DNS authority document exists but lists no verified zones. '
112
+ + 'dcrouter is authoritative for nothing and will REFUSE every query until a zone is verified.'
113
+ : 'DnsAuthorityManager: no DNS authority document exists yet. dcrouter is authoritative for nothing '
114
+ + 'and will REFUSE every query until a zone is verified. Seed the document, or claim each delegated '
115
+ + 'zone with dns-authority:write (probe first, then verify).', { zone: 'dns', authorityState: 'empty' });
116
+ }
117
+ async stop() {
118
+ this.verifiedZones = [];
119
+ this.state = 'unavailable';
120
+ this.reconciler = undefined;
121
+ }
122
+ /**
123
+ * Wire the callback that re-derives runtime state (zone handlers, route
124
+ * certificate warnings, private-route overlay) after the effective set changes.
125
+ */
126
+ setReconciler(reconciler) {
127
+ this.reconciler = reconciler;
128
+ }
129
+ // ==========================================================================
130
+ // Reads
131
+ // ==========================================================================
132
+ /**
133
+ * The authority set: delegation-verified zones, sorted so every consumer that
134
+ * needs a stable "first zone" (the DNSSEC signing zone, for one) gets the same
135
+ * answer across restarts regardless of database insertion order.
136
+ *
137
+ * This is what the ownership predicate consumes, and it is the only input to
138
+ * it. There is no bootstrap contribution.
139
+ */
140
+ getEffectiveZoneNames() {
141
+ const zones = new Set();
142
+ for (const verified of this.verifiedZones) {
143
+ const normalized = normalizeZone(verified.zone);
144
+ if (normalized)
145
+ zones.add(normalized);
146
+ }
147
+ return [...zones].sort();
148
+ }
149
+ /** Whether the stored authority set was readable when it was last loaded. */
150
+ getState() {
151
+ return this.state;
152
+ }
153
+ getSettings() {
154
+ return {
155
+ zones: this.verifiedZones
156
+ .map((entry) => ({ ...entry, zone: normalizeZone(entry.zone), origin: 'verified' }))
157
+ .sort((a, b) => a.zone.localeCompare(b.zone)),
158
+ state: this.state,
159
+ expectedNameservers: this.getExpectedNameservers().map(normalizeNameserver).filter(Boolean),
160
+ updatedAt: this.updatedAt,
161
+ updatedBy: this.updatedBy,
162
+ };
163
+ }
164
+ // ==========================================================================
165
+ // Delegation probe
166
+ // ==========================================================================
167
+ /**
168
+ * Ask the public DNS whether `zone` is delegated to our nameservers.
169
+ *
170
+ * Deliberately uses `strategy: 'doh'` — a single DNS-over-HTTPS attempt
171
+ * against a public resolver, with no system-resolver fallback. smartdns'
172
+ * `getNameServers()` uses `dns.resolveNs`, i.e. the *system* resolver, which on
173
+ * a dcrouter host may be dcrouter itself: it would happily answer with the very
174
+ * NS records we generated, making the probe self-confirming. An independent
175
+ * vantage point is the whole point of the proof.
176
+ *
177
+ * The three verdicts are kept distinct on purpose. A timeout is not evidence
178
+ * that a zone is not ours; collapsing 'undeterminable' into 'not-delegated'
179
+ * would let a transient resolver fault revoke authority.
180
+ */
181
+ async probeDelegation(zoneArg) {
182
+ const zone = normalizeZone(zoneArg);
183
+ const expectedNameservers = this.getExpectedNameservers()
184
+ .map(normalizeNameserver)
185
+ .filter(Boolean);
186
+ if (!isUsableZoneName(zone)) {
187
+ return {
188
+ zone,
189
+ verdict: 'not-delegated',
190
+ observedNameservers: [],
191
+ expectedNameservers,
192
+ detail: `'${zoneArg}' is not a usable zone name`,
193
+ };
194
+ }
195
+ if (expectedNameservers.length === 0) {
196
+ return {
197
+ zone,
198
+ verdict: 'undeterminable',
199
+ observedNameservers: [],
200
+ expectedNameservers,
201
+ detail: 'dnsNsDomains is not configured, so there is no nameserver identity to prove delegation against',
202
+ };
203
+ }
204
+ const smartdnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
205
+ strategy: 'doh',
206
+ allowDohFallback: false,
207
+ timeoutMs: 10_000,
208
+ });
209
+ let result;
210
+ try {
211
+ result = await smartdnsClient.queryRecords(zone, 'NS');
212
+ }
213
+ catch (error) {
214
+ return {
215
+ zone,
216
+ verdict: 'undeterminable',
217
+ observedNameservers: [],
218
+ expectedNameservers,
219
+ detail: `delegation lookup threw: ${error.message}`,
220
+ };
221
+ }
222
+ if (result.status === 'error') {
223
+ return {
224
+ zone,
225
+ verdict: 'undeterminable',
226
+ observedNameservers: [],
227
+ expectedNameservers,
228
+ detail: `delegation lookup failed (${result.error.kind}/${result.error.code}): ${result.error.message}`,
229
+ };
230
+ }
231
+ if (result.status === 'missing') {
232
+ return {
233
+ zone,
234
+ verdict: 'not-delegated',
235
+ observedNameservers: [],
236
+ expectedNameservers,
237
+ detail: result.missingReason === 'nxdomain'
238
+ ? `${zone} does not exist in public DNS`
239
+ : `${zone} has no NS records in public DNS`,
240
+ };
241
+ }
242
+ const observedNameservers = [...new Set(result.records
243
+ .map((record) => normalizeNameserver(String(record.value ?? '')))
244
+ .filter((nameserver) => Boolean(nameserver)))];
245
+ const matches = observedNameservers.filter((observed) => expectedNameservers.includes(observed));
246
+ if (matches.length === 0) {
247
+ return {
248
+ zone,
249
+ verdict: 'not-delegated',
250
+ observedNameservers,
251
+ expectedNameservers,
252
+ detail: `${zone} is delegated to ${observedNameservers.join(', ') || 'nothing'}, which does not include any of our nameservers (${expectedNameservers.join(', ')})`,
253
+ };
254
+ }
255
+ return {
256
+ zone,
257
+ verdict: 'delegated',
258
+ observedNameservers,
259
+ expectedNameservers,
260
+ detail: `${zone} is delegated to ${matches.join(', ')}`,
261
+ };
262
+ }
263
+ // ==========================================================================
264
+ // Mutations
265
+ // ==========================================================================
266
+ /**
267
+ * Claim authority over a zone, but only against a positive delegation proof.
268
+ * An undeterminable probe refuses the mutation — never assume either way.
269
+ */
270
+ async verifyZone(zoneArg, verifiedBy) {
271
+ const zone = normalizeZone(zoneArg);
272
+ if (!isUsableZoneName(zone)) {
273
+ return { success: false, message: `'${zoneArg}' is not a usable zone name` };
274
+ }
275
+ const probe = await this.probeDelegation(zone);
276
+ if (probe.verdict !== 'delegated') {
277
+ return {
278
+ success: false,
279
+ probe,
280
+ message: probe.verdict === 'undeterminable'
281
+ ? `Cannot verify ${zone}: ${probe.detail}. Authority is not claimed on an undeterminable probe.`
282
+ : `Cannot verify ${zone}: ${probe.detail}`,
283
+ };
284
+ }
285
+ const entry = {
286
+ zone,
287
+ origin: 'verified',
288
+ verifiedAt: Date.now(),
289
+ observedNameservers: probe.observedNameservers,
290
+ verifiedBy,
291
+ };
292
+ const nextZones = [
293
+ ...this.verifiedZones.filter((candidate) => normalizeZone(candidate.zone) !== zone),
294
+ entry,
295
+ ];
296
+ const applied = await this.applyZones(nextZones, verifiedBy, `authority claimed for ${zone}`);
297
+ if (!applied.success) {
298
+ return { ...applied, probe };
299
+ }
300
+ logger.log('info', `DnsAuthorityManager: ${zone} verified as ours (delegated to ${probe.observedNameservers.join(', ')}) and applied without a restart`, { zone: 'dns', verifiedBy });
301
+ return { success: true, probe, settings: this.getSettings() };
302
+ }
303
+ /**
304
+ * Drop a verified zone. Every zone is revocable — there is no undroppable
305
+ * deployment-declared floor any more, so withdrawing authority never needs a
306
+ * redeploy and never leaves the router asserting a claim it cannot retract.
307
+ */
308
+ async revokeZone(zoneArg, updatedBy) {
309
+ const zone = normalizeZone(zoneArg);
310
+ if (!this.verifiedZones.some((candidate) => normalizeZone(candidate.zone) === zone)) {
311
+ return { success: false, message: `${zone} is not a verified zone` };
312
+ }
313
+ const nextZones = this.verifiedZones.filter((candidate) => normalizeZone(candidate.zone) !== zone);
314
+ const applied = await this.applyZones(nextZones, updatedBy, `authority revoked for ${zone}`);
315
+ if (!applied.success) {
316
+ return applied;
317
+ }
318
+ logger.log('info', `DnsAuthorityManager: authority over ${zone} revoked and torn down in-process`, {
319
+ zone: 'dns',
320
+ updatedBy,
321
+ });
322
+ return { success: true, settings: this.getSettings() };
323
+ }
324
+ /**
325
+ * Persist a new zone set and reconcile the running process against it.
326
+ *
327
+ * Fail closed: if reconciliation throws, the previous set is restored in the
328
+ * database and re-reconciled, so the router is never left half-converted.
329
+ * Reconciliation is idempotent, which is what makes the rollback safe.
330
+ */
331
+ async applyZones(nextZones, updatedBy, reason) {
332
+ const previousZones = [...this.verifiedZones];
333
+ const previousUpdatedAt = this.updatedAt;
334
+ const previousUpdatedBy = this.updatedBy;
335
+ this.verifiedZones = nextZones;
336
+ this.updatedAt = Date.now();
337
+ this.updatedBy = updatedBy;
338
+ try {
339
+ await this.persist();
340
+ }
341
+ catch (error) {
342
+ this.verifiedZones = previousZones;
343
+ this.updatedAt = previousUpdatedAt;
344
+ this.updatedBy = previousUpdatedBy;
345
+ return { success: false, message: `Failed to persist DNS authority: ${error.message}` };
346
+ }
347
+ try {
348
+ await this.reconciler?.(reason);
349
+ }
350
+ catch (error) {
351
+ const applyError = error.message;
352
+ this.verifiedZones = previousZones;
353
+ this.updatedAt = previousUpdatedAt;
354
+ this.updatedBy = previousUpdatedBy;
355
+ let rollbackNote = '';
356
+ try {
357
+ await this.persist();
358
+ await this.reconciler?.(`rollback of ${reason}`);
359
+ }
360
+ catch (rollbackError) {
361
+ rollbackNote = ` Rollback also failed: ${rollbackError.message}. `
362
+ + 'Runtime DNS state may be inconsistent — restart dcrouter to rebuild it deterministically.';
363
+ logger.log('error', `DnsAuthorityManager: rollback failed after ${reason}.${rollbackNote}`, {
364
+ zone: 'dns',
365
+ });
366
+ }
367
+ return {
368
+ success: false,
369
+ message: `Failed to apply DNS authority change (${reason}): ${applyError}. `
370
+ + `The change was rolled back and nothing was left half-applied.${rollbackNote}`,
371
+ };
372
+ }
373
+ return { success: true, settings: this.getSettings() };
374
+ }
375
+ async persist() {
376
+ let doc = await DnsAuthorityDoc.load();
377
+ if (!doc) {
378
+ doc = new DnsAuthorityDoc();
379
+ doc.settingsId = 'dns-authority-settings';
380
+ }
381
+ doc.verifiedZones = this.verifiedZones;
382
+ doc.updatedAt = this.updatedAt;
383
+ doc.updatedBy = this.updatedBy;
384
+ await doc.save();
385
+ }
386
+ // ==========================================================================
387
+ // Drift audit
388
+ // ==========================================================================
389
+ /**
390
+ * Compare claimed authority against what is actually true, in every direction.
391
+ *
392
+ * This is the check whose absence let one zone sit declared in `dnsScopes`
393
+ * while four live zones were delegated to our nameservers and unclaimed.
394
+ *
395
+ * Advisory by design. It never mutates the authority set: a resolver blip at
396
+ * startup must not revoke authority for every zone and convert a transient
397
+ * fault into the outage this whole path exists to avoid. Undeterminable probes
398
+ * are skipped rather than reported as drift.
399
+ */
400
+ async auditDelegationDrift() {
401
+ const drift = [];
402
+ const effectiveZones = new Set(this.getEffectiveZoneNames());
403
+ const domains = await DomainDoc.findAll();
404
+ const hostedZones = new Set(domains
405
+ .filter((domainArg) => domainArg.source === 'dcrouter')
406
+ .map((domainArg) => normalizeZone(domainArg.name))
407
+ .filter(Boolean));
408
+ // Direction 1: a dcrouter-hosted zone delegated to us but not claimed.
409
+ for (const zone of [...hostedZones].filter((zone) => !effectiveZones.has(zone))) {
410
+ const probe = await this.probeDelegation(zone);
411
+ if (probe.verdict !== 'delegated')
412
+ continue;
413
+ drift.push({
414
+ zone,
415
+ kind: 'delegated-but-unclaimed',
416
+ observedNameservers: probe.observedNameservers,
417
+ expectedNameservers: probe.expectedNameservers,
418
+ detail: `${zone} is delegated to ${probe.observedNameservers.join(', ')} but is not in the authority set, `
419
+ + 'so dcrouter refuses to serve it authoritatively and will not request certificates for it',
420
+ });
421
+ }
422
+ // Direction 2: a verified zone whose delegation moved away from us.
423
+ for (const verified of this.verifiedZones) {
424
+ const zone = normalizeZone(verified.zone);
425
+ const probe = await this.probeDelegation(zone);
426
+ if (probe.verdict !== 'not-delegated')
427
+ continue;
428
+ drift.push({
429
+ zone,
430
+ kind: 'claimed-but-not-delegated',
431
+ observedNameservers: probe.observedNameservers,
432
+ expectedNameservers: probe.expectedNameservers,
433
+ detail: `${zone} was verified at ${verified.verifiedAt ? new Date(verified.verifiedAt).toISOString() : 'an unknown time'} `
434
+ + `but its delegation now names ${probe.observedNameservers.join(', ') || 'nothing'}; `
435
+ + 'dcrouter is still claiming authority over a zone that is no longer delegated to it',
436
+ });
437
+ }
438
+ // Direction 3: a claimed zone with nothing behind it. Records and generated
439
+ // apex NS both hang off a dcrouter-hosted DomainDoc, so authority without
440
+ // one is a zone we answer for and have nothing to say about — a lame
441
+ // delegation of our own making. Cheap to detect and impossible to notice
442
+ // otherwise, because the zone REFUSES nothing and answers nothing.
443
+ for (const zone of effectiveZones) {
444
+ if (hostedZones.has(zone))
445
+ continue;
446
+ drift.push({
447
+ zone,
448
+ kind: 'verified-but-unhosted',
449
+ observedNameservers: [],
450
+ expectedNameservers: this.getExpectedNameservers().map(normalizeNameserver).filter(Boolean),
451
+ detail: `${zone} is in the authority set but has no dcrouter-hosted domain, so no records and no `
452
+ + 'generated apex NS are served for it; create the domain in the DNS manager or revoke the authority claim',
453
+ });
454
+ }
455
+ return drift;
456
+ }
457
+ /** Run the audit and log every finding at `error`. Never throws. */
458
+ async logDelegationDrift() {
459
+ let drift;
460
+ try {
461
+ drift = await this.auditDelegationDrift();
462
+ }
463
+ catch (error) {
464
+ logger.log('warn', `DnsAuthorityManager: delegation drift audit could not complete: ${error.message}`, { zone: 'dns' });
465
+ return;
466
+ }
467
+ for (const entry of drift) {
468
+ logger.log('error', `DNS authority drift (${entry.kind}): ${entry.detail}`, {
469
+ zone: 'dns',
470
+ driftKind: entry.kind,
471
+ driftZone: entry.zone,
472
+ });
473
+ }
474
+ if (drift.length === 0) {
475
+ logger.log('info', 'DnsAuthorityManager: declared authority matches observed delegation', {
476
+ zone: 'dns',
477
+ });
478
+ }
479
+ }
480
+ }
481
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuYWdlci5kbnMtYXV0aG9yaXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvZG5zL21hbmFnZXIuZG5zLWF1dGhvcml0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxlQUFlLEVBQUUsU0FBUyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUF1QnRFLE1BQU0sYUFBYSxHQUFHLENBQUMsT0FBZSxFQUFVLEVBQUUsQ0FDaEQsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFbEQsZ0VBQWdFO0FBQ2hFLE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxhQUFxQixFQUFVLEVBQUUsQ0FDNUQsYUFBYSxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFeEQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLE9BQWUsRUFBVyxFQUFFO0lBQ3BELElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLE1BQU0sR0FBRyxHQUFHO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDbkQsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUMxRixNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ2xDLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDcEMsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO1dBQzlDLFFBQVEsQ0FBQyxNQUFNLElBQUksRUFBRTtXQUNyQixtQ0FBbUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUMzRCxDQUFDLENBQUM7QUFFRjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9ERztBQUNILE1BQU0sT0FBTyxtQkFBbUI7SUFRcEI7SUFQRixhQUFhLEdBQXdCLEVBQUUsQ0FBQztJQUN4QyxTQUFTLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsU0FBUyxHQUFHLEVBQUUsQ0FBQztJQUNmLEtBQUssR0FBdUIsYUFBYSxDQUFDO0lBQzFDLFVBQVUsQ0FBMkI7SUFFN0MsWUFDVSxzQkFBc0M7UUFBdEMsMkJBQXNCLEdBQXRCLHNCQUFzQixDQUFnQjtJQUM3QyxDQUFDO0lBRUosNkVBQTZFO0lBQzdFLFlBQVk7SUFDWiw2RUFBNkU7SUFFdEUsS0FBSyxDQUFDLEtBQUs7UUFDaEIsSUFBSSxHQUEyQixDQUFDO1FBQ2hDLElBQUksQ0FBQztZQUNILEdBQUcsR0FBRyxNQUFNLGVBQWUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNyQyxDQUFDO1FBQUMsT0FBTyxLQUFjLEVBQUUsQ0FBQztZQUN4Qix1RUFBdUU7WUFDdkUsa0VBQWtFO1lBQ2xFLElBQUksQ0FBQyxLQUFLLEdBQUcsYUFBYSxDQUFDO1lBQzNCLElBQUksQ0FBQyxhQUFhLEdBQUcsRUFBRSxDQUFDO1lBQ3hCLE1BQU0sSUFBSSxLQUFLLENBQ2Isb0VBQXFFLEtBQWUsQ0FBQyxPQUFPLEtBQUs7a0JBQzdGLG9HQUFvRztrQkFDcEcsMEVBQTBFLENBQy9FLENBQUM7UUFDSixDQUFDO1FBRUQsSUFBSSxDQUFDLEtBQUssR0FBRyxRQUFRLENBQUM7UUFDdEIsSUFBSSxDQUFDLGFBQWEsR0FBRyxHQUFHLEVBQUUsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDdEUsSUFBSSxDQUFDLFNBQVMsR0FBRyxHQUFHLEVBQUUsU0FBUyxJQUFJLENBQUMsQ0FBQztRQUNyQyxJQUFJLENBQUMsU0FBUyxHQUFHLEdBQUcsRUFBRSxTQUFTLElBQUksRUFBRSxDQUFDO1FBRXRDLElBQUksSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDbEMsTUFBTSxDQUFDLEdBQUcsQ0FDUixNQUFNLEVBQ04sd0JBQXdCLElBQUksQ0FBQyxhQUFhLENBQUMsTUFBTSxzQ0FBc0M7a0JBQ25GLElBQUksSUFBSSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFDbEYsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQ2hCLENBQUM7WUFDRixPQUFPO1FBQ1QsQ0FBQztRQUVELHVFQUF1RTtRQUN2RSxtRUFBbUU7UUFDbkUsTUFBTSxDQUFDLEdBQUcsQ0FDUixPQUFPLEVBQ1AsR0FBRztZQUNELENBQUMsQ0FBQyxzRkFBc0Y7a0JBQ3BGLDZGQUE2RjtZQUNqRyxDQUFDLENBQUMsbUdBQW1HO2tCQUNqRyxtR0FBbUc7a0JBQ25HLDJEQUEyRCxFQUNqRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsY0FBYyxFQUFFLE9BQU8sRUFBRSxDQUN6QyxDQUFDO0lBQ0osQ0FBQztJQUVNLEtBQUssQ0FBQyxJQUFJO1FBQ2YsSUFBSSxDQUFDLGFBQWEsR0FBRyxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLEtBQUssR0FBRyxhQUFhLENBQUM7UUFDM0IsSUFBSSxDQUFDLFVBQVUsR0FBRyxTQUFTLENBQUM7SUFDOUIsQ0FBQztJQUVEOzs7T0FHRztJQUNJLGFBQWEsQ0FBQyxVQUFvQztRQUN2RCxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQztJQUMvQixDQUFDO0lBRUQsNkVBQTZFO0lBQzdFLFFBQVE7SUFDUiw2RUFBNkU7SUFFN0U7Ozs7Ozs7T0FPRztJQUNJLHFCQUFxQjtRQUMxQixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO1FBQ2hDLEtBQUssTUFBTSxRQUFRLElBQUksSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1lBQzFDLE1BQU0sVUFBVSxHQUFHLGFBQWEsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDaEQsSUFBSSxVQUFVO2dCQUFFLEtBQUssQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDeEMsQ0FBQztRQUNELE9BQU8sQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFRCw2RUFBNkU7SUFDdEUsUUFBUTtRQUNiLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQztJQUNwQixDQUFDO0lBRU0sV0FBVztRQUNoQixPQUFPO1lBQ0wsS0FBSyxFQUFFLElBQUksQ0FBQyxhQUFhO2lCQUN0QixHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxHQUFHLEtBQUssRUFBRSxJQUFJLEVBQUUsYUFBYSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsVUFBbUIsRUFBRSxDQUFDLENBQUM7aUJBQzVGLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMvQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUs7WUFDakIsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQztZQUMzRixTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVM7WUFDekIsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTO1NBQzFCLENBQUM7SUFDSixDQUFDO0lBRUQsNkVBQTZFO0lBQzdFLG1CQUFtQjtJQUNuQiw2RUFBNkU7SUFFN0U7Ozs7Ozs7Ozs7Ozs7T0FhRztJQUNJLEtBQUssQ0FBQyxlQUFlLENBQUMsT0FBZTtRQUMxQyxNQUFNLElBQUksR0FBRyxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDcEMsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUMsc0JBQXNCLEVBQUU7YUFDdEQsR0FBRyxDQUFDLG1CQUFtQixDQUFDO2FBQ3hCLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUVuQixJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUM1QixPQUFPO2dCQUNMLElBQUk7Z0JBQ0osT0FBTyxFQUFFLGVBQWU7Z0JBQ3hCLG1CQUFtQixFQUFFLEVBQUU7Z0JBQ3ZCLG1CQUFtQjtnQkFDbkIsTUFBTSxFQUFFLElBQUksT0FBTyw2QkFBNkI7YUFDakQsQ0FBQztRQUNKLENBQUM7UUFDRCxJQUFJLG1CQUFtQixDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNyQyxPQUFPO2dCQUNMLElBQUk7Z0JBQ0osT0FBTyxFQUFFLGdCQUFnQjtnQkFDekIsbUJBQW1CLEVBQUUsRUFBRTtnQkFDdkIsbUJBQW1CO2dCQUNuQixNQUFNLEVBQUUsZ0dBQWdHO2FBQ3pHLENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxjQUFjLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUM7WUFDaEUsUUFBUSxFQUFFLEtBQUs7WUFDZixnQkFBZ0IsRUFBRSxLQUFLO1lBQ3ZCLFNBQVMsRUFBRSxNQUFNO1NBQ2xCLENBQUMsQ0FBQztRQUVILElBQUksTUFBK0QsQ0FBQztRQUNwRSxJQUFJLENBQUM7WUFDSCxNQUFNLEdBQUcsTUFBTSxjQUFjLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztRQUN6RCxDQUFDO1FBQUMsT0FBTyxLQUFjLEVBQUUsQ0FBQztZQUN4QixPQUFPO2dCQUNMLElBQUk7Z0JBQ0osT0FBTyxFQUFFLGdCQUFnQjtnQkFDekIsbUJBQW1CLEVBQUUsRUFBRTtnQkFDdkIsbUJBQW1CO2dCQUNuQixNQUFNLEVBQUUsNEJBQTZCLEtBQWUsQ0FBQyxPQUFPLEVBQUU7YUFDL0QsQ0FBQztRQUNKLENBQUM7UUFFRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssT0FBTyxFQUFFLENBQUM7WUFDOUIsT0FBTztnQkFDTCxJQUFJO2dCQUNKLE9BQU8sRUFBRSxnQkFBZ0I7Z0JBQ3pCLG1CQUFtQixFQUFFLEVBQUU7Z0JBQ3ZCLG1CQUFtQjtnQkFDbkIsTUFBTSxFQUFFLDZCQUE2QixNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksTUFBTSxNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sRUFBRTthQUN4RyxDQUFDO1FBQ0osQ0FBQztRQUVELElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUNoQyxPQUFPO2dCQUNMLElBQUk7Z0JBQ0osT0FBTyxFQUFFLGVBQWU7Z0JBQ3hCLG1CQUFtQixFQUFFLEVBQUU7Z0JBQ3ZCLG1CQUFtQjtnQkFDbkIsTUFBTSxFQUFFLE1BQU0sQ0FBQyxhQUFhLEtBQUssVUFBVTtvQkFDekMsQ0FBQyxDQUFDLEdBQUcsSUFBSSwrQkFBK0I7b0JBQ3hDLENBQUMsQ0FBQyxHQUFHLElBQUksa0NBQWtDO2FBQzlDLENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxtQkFBbUIsR0FBYSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQy9DLE1BQU0sQ0FBQyxPQUFPO2lCQUNYLEdBQUcsQ0FBQyxDQUFDLE1BQU0sRUFBVSxFQUFFLENBQUMsbUJBQW1CLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztpQkFDeEUsTUFBTSxDQUFDLENBQUMsVUFBVSxFQUF3QixFQUFFLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQ3JFLENBQUMsQ0FBQztRQUNILE1BQU0sT0FBTyxHQUFHLG1CQUFtQixDQUFDLE1BQU0sQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFDakcsSUFBSSxPQUFPLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ3pCLE9BQU87Z0JBQ0wsSUFBSTtnQkFDSixPQUFPLEVBQUUsZUFBZTtnQkFDeEIsbUJBQW1CO2dCQUNuQixtQkFBbUI7Z0JBQ25CLE1BQU0sRUFBRSxHQUFHLElBQUksb0JBQW9CLG1CQUFtQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxTQUFTLG9EQUFvRCxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUc7YUFDcEssQ0FBQztRQUNKLENBQUM7UUFDRCxPQUFPO1lBQ0wsSUFBSTtZQUNKLE9BQU8sRUFBRSxXQUFXO1lBQ3BCLG1CQUFtQjtZQUNuQixtQkFBbUI7WUFDbkIsTUFBTSxFQUFFLEdBQUcsSUFBSSxvQkFBb0IsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTtTQUN4RCxDQUFDO0lBQ0osQ0FBQztJQUVELDZFQUE2RTtJQUM3RSxZQUFZO0lBQ1osNkVBQTZFO0lBRTdFOzs7T0FHRztJQUNJLEtBQUssQ0FBQyxVQUFVLENBQUMsT0FBZSxFQUFFLFVBQWtCO1FBQ3pELE1BQU0sSUFBSSxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNwQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUM1QixPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsSUFBSSxPQUFPLDZCQUE2QixFQUFFLENBQUM7UUFDL0UsQ0FBQztRQUVELE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMvQyxJQUFJLEtBQUssQ0FBQyxPQUFPLEtBQUssV0FBVyxFQUFFLENBQUM7WUFDbEMsT0FBTztnQkFDTCxPQUFPLEVBQUUsS0FBSztnQkFDZCxLQUFLO2dCQUNMLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTyxLQUFLLGdCQUFnQjtvQkFDekMsQ0FBQyxDQUFDLGlCQUFpQixJQUFJLEtBQUssS0FBSyxDQUFDLE1BQU0sd0RBQXdEO29CQUNoRyxDQUFDLENBQUMsaUJBQWlCLElBQUksS0FBSyxLQUFLLENBQUMsTUFBTSxFQUFFO2FBQzdDLENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxLQUFLLEdBQXNCO1lBQy9CLElBQUk7WUFDSixNQUFNLEVBQUUsVUFBVTtZQUNsQixVQUFVLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRTtZQUN0QixtQkFBbUIsRUFBRSxLQUFLLENBQUMsbUJBQW1CO1lBQzlDLFVBQVU7U0FDWCxDQUFDO1FBQ0YsTUFBTSxTQUFTLEdBQUc7WUFDaEIsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxJQUFJLENBQUM7WUFDbkYsS0FBSztTQUNOLENBQUM7UUFDRixNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsU0FBUyxFQUFFLFVBQVUsRUFBRSx5QkFBeUIsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUM5RixJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3JCLE9BQU8sRUFBRSxHQUFHLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQztRQUMvQixDQUFDO1FBQ0QsTUFBTSxDQUFDLEdBQUcsQ0FDUixNQUFNLEVBQ04sd0JBQXdCLElBQUksbUNBQW1DLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGlDQUFpQyxFQUNwSSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLENBQzVCLENBQUM7UUFDRixPQUFPLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ2hFLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksS0FBSyxDQUFDLFVBQVUsQ0FBQyxPQUFlLEVBQUUsU0FBaUI7UUFDeEQsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ3BDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ3BGLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxHQUFHLElBQUkseUJBQXlCLEVBQUUsQ0FBQztRQUN2RSxDQUFDO1FBQ0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLGFBQWEsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxDQUFDLENBQUM7UUFDbkcsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsRUFBRSxTQUFTLEVBQUUseUJBQXlCLElBQUksRUFBRSxDQUFDLENBQUM7UUFDN0YsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNyQixPQUFPLE9BQU8sQ0FBQztRQUNqQixDQUFDO1FBQ0QsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsdUNBQXVDLElBQUksbUNBQW1DLEVBQUU7WUFDakcsSUFBSSxFQUFFLEtBQUs7WUFDWCxTQUFTO1NBQ1YsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ3pELENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSyxLQUFLLENBQUMsVUFBVSxDQUN0QixTQUE4QixFQUM5QixTQUFpQixFQUNqQixNQUFjO1FBRWQsTUFBTSxhQUFhLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUM5QyxNQUFNLGlCQUFpQixHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDekMsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO1FBRXpDLElBQUksQ0FBQyxhQUFhLEdBQUcsU0FBUyxDQUFDO1FBQy9CLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQzVCLElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO1FBRTNCLElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3ZCLENBQUM7UUFBQyxPQUFPLEtBQWMsRUFBRSxDQUFDO1lBQ3hCLElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFDO1lBQ25DLElBQUksQ0FBQyxTQUFTLEdBQUcsaUJBQWlCLENBQUM7WUFDbkMsSUFBSSxDQUFDLFNBQVMsR0FBRyxpQkFBaUIsQ0FBQztZQUNuQyxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsb0NBQXFDLEtBQWUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDO1FBQ3JHLENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCxNQUFNLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNsQyxDQUFDO1FBQUMsT0FBTyxLQUFjLEVBQUUsQ0FBQztZQUN4QixNQUFNLFVBQVUsR0FBSSxLQUFlLENBQUMsT0FBTyxDQUFDO1lBQzVDLElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFDO1lBQ25DLElBQUksQ0FBQyxTQUFTLEdBQUcsaUJBQWlCLENBQUM7WUFDbkMsSUFBSSxDQUFDLFNBQVMsR0FBRyxpQkFBaUIsQ0FBQztZQUNuQyxJQUFJLFlBQVksR0FBRyxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDO2dCQUNILE1BQU0sSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNyQixNQUFNLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQyxlQUFlLE1BQU0sRUFBRSxDQUFDLENBQUM7WUFDbkQsQ0FBQztZQUFDLE9BQU8sYUFBc0IsRUFBRSxDQUFDO2dCQUNoQyxZQUFZLEdBQUcsMEJBQTJCLGFBQXVCLENBQUMsT0FBTyxJQUFJO3NCQUN6RSwyRkFBMkYsQ0FBQztnQkFDaEcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsOENBQThDLE1BQU0sSUFBSSxZQUFZLEVBQUUsRUFBRTtvQkFDMUYsSUFBSSxFQUFFLEtBQUs7aUJBQ1osQ0FBQyxDQUFDO1lBQ0wsQ0FBQztZQUNELE9BQU87Z0JBQ0wsT0FBTyxFQUFFLEtBQUs7Z0JBQ2QsT0FBTyxFQUFFLHlDQUF5QyxNQUFNLE1BQU0sVUFBVSxJQUFJO3NCQUN4RSxnRUFBZ0UsWUFBWSxFQUFFO2FBQ25GLENBQUM7UUFDSixDQUFDO1FBRUQsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ3pELENBQUM7SUFFTyxLQUFLLENBQUMsT0FBTztRQUNuQixJQUFJLEdBQUcsR0FBRyxNQUFNLGVBQWUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN2QyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDVCxHQUFHLEdBQUcsSUFBSSxlQUFlLEVBQUUsQ0FBQztZQUM1QixHQUFHLENBQUMsVUFBVSxHQUFHLHdCQUF3QixDQUFDO1FBQzVDLENBQUM7UUFDRCxHQUFHLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUM7UUFDdkMsR0FBRyxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDO1FBQy9CLEdBQUcsQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQztRQUMvQixNQUFNLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNuQixDQUFDO0lBRUQsNkVBQTZFO0lBQzdFLGNBQWM7SUFDZCw2RUFBNkU7SUFFN0U7Ozs7Ozs7Ozs7T0FVRztJQUNJLEtBQUssQ0FBQyxvQkFBb0I7UUFDL0IsTUFBTSxLQUFLLEdBQXlCLEVBQUUsQ0FBQztRQUN2QyxNQUFNLGNBQWMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQyxDQUFDO1FBRTdELE1BQU0sT0FBTyxHQUFHLE1BQU0sU0FBUyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQzFDLE1BQU0sV0FBVyxHQUFHLElBQUksR0FBRyxDQUN6QixPQUFPO2FBQ0osTUFBTSxDQUFDLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQyxTQUFTLENBQUMsTUFBTSxLQUFLLFVBQVUsQ0FBQzthQUN0RCxHQUFHLENBQUMsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLGFBQWEsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7YUFDakQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUNuQixDQUFDO1FBRUYsdUVBQXVFO1FBQ3ZFLEtBQUssTUFBTSxJQUFJLElBQUksQ0FBQyxHQUFHLFdBQVcsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNoRixNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDL0MsSUFBSSxLQUFLLENBQUMsT0FBTyxLQUFLLFdBQVc7Z0JBQUUsU0FBUztZQUM1QyxLQUFLLENBQUMsSUFBSSxDQUFDO2dCQUNULElBQUk7Z0JBQ0osSUFBSSxFQUFFLHlCQUF5QjtnQkFDL0IsbUJBQW1CLEVBQUUsS0FBSyxDQUFDLG1CQUFtQjtnQkFDOUMsbUJBQW1CLEVBQUUsS0FBSyxDQUFDLG1CQUFtQjtnQkFDOUMsTUFBTSxFQUFFLEdBQUcsSUFBSSxvQkFBb0IsS0FBSyxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsb0NBQW9DO3NCQUN2RywwRkFBMEY7YUFDL0YsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELG9FQUFvRTtRQUNwRSxLQUFLLE1BQU0sUUFBUSxJQUFJLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUMxQyxNQUFNLElBQUksR0FBRyxhQUFhLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzFDLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMvQyxJQUFJLEtBQUssQ0FBQyxPQUFPLEtBQUssZUFBZTtnQkFBRSxTQUFTO1lBQ2hELEtBQUssQ0FBQyxJQUFJLENBQUM7Z0JBQ1QsSUFBSTtnQkFDSixJQUFJLEVBQUUsMkJBQTJCO2dCQUNqQyxtQkFBbUIsRUFBRSxLQUFLLENBQUMsbUJBQW1CO2dCQUM5QyxtQkFBbUIsRUFBRSxLQUFLLENBQUMsbUJBQW1CO2dCQUM5QyxNQUFNLEVBQUUsR0FBRyxJQUFJLG9CQUFvQixRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixHQUFHO3NCQUN2SCxnQ0FBZ0MsS0FBSyxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxTQUFTLElBQUk7c0JBQ3JGLG9GQUFvRjthQUN6RixDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsNEVBQTRFO1FBQzVFLDBFQUEwRTtRQUMxRSxxRUFBcUU7UUFDckUseUVBQXlFO1FBQ3pFLG1FQUFtRTtRQUNuRSxLQUFLLE1BQU0sSUFBSSxJQUFJLGNBQWMsRUFBRSxDQUFDO1lBQ2xDLElBQUksV0FBVyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUM7Z0JBQUUsU0FBUztZQUNwQyxLQUFLLENBQUMsSUFBSSxDQUFDO2dCQUNULElBQUk7Z0JBQ0osSUFBSSxFQUFFLHVCQUF1QjtnQkFDN0IsbUJBQW1CLEVBQUUsRUFBRTtnQkFDdkIsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQztnQkFDM0YsTUFBTSxFQUFFLEdBQUcsSUFBSSxtRkFBbUY7c0JBQzlGLHlHQUF5RzthQUM5RyxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsb0VBQW9FO0lBQzdELEtBQUssQ0FBQyxrQkFBa0I7UUFDN0IsSUFBSSxLQUEyQixDQUFDO1FBQ2hDLElBQUksQ0FBQztZQUNILEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1FBQzVDLENBQUM7UUFBQyxPQUFPLEtBQWMsRUFBRSxDQUFDO1lBQ3hCLE1BQU0sQ0FBQyxHQUFHLENBQ1IsTUFBTSxFQUNOLG1FQUFvRSxLQUFlLENBQUMsT0FBTyxFQUFFLEVBQzdGLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUNoQixDQUFDO1lBQ0YsT0FBTztRQUNULENBQUM7UUFDRCxLQUFLLE1BQU0sS0FBSyxJQUFJLEtBQUssRUFBRSxDQUFDO1lBQzFCLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLHdCQUF3QixLQUFLLENBQUMsSUFBSSxNQUFNLEtBQUssQ0FBQyxNQUFNLEVBQUUsRUFBRTtnQkFDMUUsSUFBSSxFQUFFLEtBQUs7Z0JBQ1gsU0FBUyxFQUFFLEtBQUssQ0FBQyxJQUFJO2dCQUNyQixTQUFTLEVBQUUsS0FBSyxDQUFDLElBQUk7YUFDdEIsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUNELElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN2QixNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxxRUFBcUUsRUFBRTtnQkFDeEYsSUFBSSxFQUFFLEtBQUs7YUFDWixDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=