@serve.zone/dcrouter 18.7.1 → 18.8.0

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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '18.7.1',
6
+ version: '18.8.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "18.7.1",
4
+ "version": "18.8.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -16,59 +16,60 @@
16
16
  "license": "MIT",
17
17
  "devDependencies": {
18
18
  "@git.zone/tsbuild": "^4.4.2",
19
- "@git.zone/tsbundle": "^2.11.3",
19
+ "@git.zone/tsbundle": "^2.11.4",
20
20
  "@git.zone/tsdeno": "^1.7.0",
21
- "@git.zone/tsdocker": "^3.1.1",
21
+ "@git.zone/tsdocker": "^3.4.2",
22
22
  "@git.zone/tsrun": "^2.0.6",
23
- "@git.zone/tstest": "^3.6.7",
23
+ "@git.zone/tstest": "^4.0.0",
24
24
  "@git.zone/tswatch": "^3.3.5",
25
- "@types/node": "26.1.1",
25
+ "@push.rocks/smartdb-v2": "npm:@push.rocks/smartdb@2.14.6",
26
+ "@types/node": "26.2.0",
26
27
  "@types/web-push": "3.6.4"
27
28
  },
28
29
  "dependencies": {
29
- "@api.global/typedrequest": "^3.3.2",
30
- "@api.global/typedrequest-interfaces": "^3.0.19",
31
- "@api.global/typedserver": "^8.6.4",
32
- "@api.global/typedsocket": "^5.0.0",
30
+ "@api.global/typedrequest": "^3.8.0",
31
+ "@api.global/typedrequest-interfaces": "^4.0.0",
32
+ "@api.global/typedserver": "^8.12.0",
33
+ "@api.global/typedsocket": "^5.1.2",
33
34
  "@apiclient.xyz/cloudflare": "^8.0.0",
34
- "@design.estate/dees-catalog": "^3.99.0",
35
+ "@design.estate/dees-catalog": "^3.104.0",
35
36
  "@design.estate/dees-element": "^2.4.0",
36
- "@idp.global/sdk": "^14.3.0",
37
+ "@idp.global/sdk": "^15.0.1",
37
38
  "@push.rocks/lik": "^6.4.1",
38
39
  "@push.rocks/projectinfo": "^5.1.0",
39
40
  "@push.rocks/qenv": "^6.1.4",
40
41
  "@push.rocks/smartacme": "^9.5.0",
41
- "@push.rocks/smartbucket": "^4.6.1",
42
+ "@push.rocks/smartbucket": "^4.13.0",
42
43
  "@push.rocks/smartchallenge": "^1.1.0",
43
44
  "@push.rocks/smartdata": "^7.4.0",
44
- "@push.rocks/smartdb": "2.14.6",
45
- "@push.rocks/smartdns": "^7.12.1",
46
- "@push.rocks/smartfs": "^1.6.0",
45
+ "@push.rocks/smartdb": "5.2.2",
46
+ "@push.rocks/smartdns": "^8.2.1",
47
+ "@push.rocks/smartfs": "^1.7.0",
47
48
  "@push.rocks/smartguard": "^3.1.2",
48
49
  "@push.rocks/smartjwt": "^2.5.1",
49
50
  "@push.rocks/smartlog": "^3.2.2",
50
51
  "@push.rocks/smartmcp": "^0.3.0",
51
52
  "@push.rocks/smartmetrics": "^3.0.3",
52
- "@push.rocks/smartmigration": "1.4.1",
53
- "@push.rocks/smartmta": "^9.4.1",
54
- "@push.rocks/smartnetwork": "^4.8.0",
53
+ "@push.rocks/smartmigration": "1.5.0",
54
+ "@push.rocks/smartmta": "^9.5.0",
55
+ "@push.rocks/smartnetwork": "^4.10.1",
55
56
  "@push.rocks/smartpath": "^6.0.0",
56
- "@push.rocks/smartpromise": "^4.2.4",
57
+ "@push.rocks/smartpromise": "^4.2.5",
57
58
  "@push.rocks/smartproxy": "^27.20.0",
58
59
  "@push.rocks/smartradius": "^1.3.0",
59
60
  "@push.rocks/smartrequest": "^5.0.4",
60
61
  "@push.rocks/smartrx": "^3.0.12",
61
- "@push.rocks/smartserve": "^2.3.0",
62
- "@push.rocks/smartstate": "^2.3.1",
62
+ "@push.rocks/smartserve": "^3.0.0",
63
+ "@push.rocks/smartstate": "^2.3.3",
63
64
  "@push.rocks/smartunique": "^3.0.10",
64
65
  "@push.rocks/smartvpn": "1.22.0",
65
66
  "@push.rocks/taskbuffer": "^8.0.2",
66
67
  "@serve.zone/catalog": "^2.21.1",
67
- "@serve.zone/interfaces": "^19.8.0",
68
- "@serve.zone/remoteingress": "^5.1.0",
68
+ "@serve.zone/interfaces": "^27.3.1",
69
+ "@serve.zone/remoteingress": "^5.1.1",
69
70
  "@tsclass/tsclass": "^9.5.1",
70
71
  "@types/qrcode": "^1.5.6",
71
- "ipaddr.js": "^2.4.0",
72
+ "ipaddr.js": "^2.5.0",
72
73
  "lru-cache": "^11.5.2",
73
74
  "qrcode": "^1.5.4",
74
75
  "uuid": "^14.0.1",
package/readme.hints.md CHANGED
@@ -26,11 +26,10 @@ Corollaries, all of which are review-blocking:
26
26
  - A registration is torn down by the same operation that removes what it was
27
27
  derived from. Track what you actually registered; do not recompute "would I
28
28
  have registered this?" at teardown time. `DnsServer.unregisterHandler(pattern,
29
- types)` removes **every** handler for that pattern/type pair, so a recomputed
30
- guess can tear down another owner's records — or miss its own when the inputs
31
- have drifted. `DnsManager.runtimeRegistrations` is the pattern: a per-domain
32
- registry of `name|type` keys, each tagged `persisted` or `generated-default`,
33
- torn down by `tearDownDomainRuntimeRegistrations(domainId)`.
29
+ types)` removes **every** handler for that pattern/type pair. `DnsManager`
30
+ retains every handle returned by `registerHandler()` under a per-domain
31
+ `name|type` key, tagged `persisted` or `generated-default`, and invokes only
32
+ those exact handles during teardown or server replacement.
34
33
  - A negative cache is invalidated by the event that **resolves its cause**, not
35
34
  only by elapsed time. Time-only expiry plus a skip-while-cooling-down rule is a
36
35
  deadlock: the sweep that would clear the entry is the sweep the entry blocks.
@@ -127,9 +126,10 @@ A timeout is not evidence that a zone is not ours. A mutation refuses on
127
126
  **The drift audit is advisory and never mutates.** It reports three directions —
128
127
  `delegated-but-unclaimed` (a live zone we could serve but do not),
129
128
  `claimed-but-not-delegated` (authority we still assert after delegation moved
130
- away), and `verified-but-unhosted` (authority with no `DomainDoc` behind it, so
131
- nothing is served for it at all). It must not revoke: a resolver blip at boot would otherwise take every
132
- zone off the air, which is the outage this path exists to avoid.
129
+ away), and `verified-but-unhosted` (authority with no `DomainDoc`-backed records
130
+ or generated apex NS). It must not revoke: a resolver blip at boot would
131
+ otherwise take every zone off the air, which is the outage this path exists to
132
+ avoid.
133
133
 
134
134
  ### Deploy sequence — seeding is mandatory, not advisable
135
135
 
@@ -144,29 +144,44 @@ restart itself introduces no reconciliation gap.
144
144
  floor left to fall back on: a zone that is not in `DnsAuthorityDoc` when the new
145
145
  build starts is REFUSED outright, and that now includes the zone that used to be
146
146
  declared in `dnsScopes` and was the only healthy one. Insert the singleton
147
- `DnsAuthorityDoc` (`settingsId: 'dns-authority-settings'`) with one
148
- `{ zone, origin: 'verified', verifiedAt, observedNameservers, verifiedBy }`
149
- entry per delegated zone *before* starting the new build. The first startup then
150
- has proof already, and the startup drift audit re-probes each entry and reports
151
- any that no longer hold, so a wrong seed is self-correcting rather than silently
152
- trusted.
147
+ `DnsAuthorityDoc` with `settingsId: 'dns-authority-settings'`, a `verifiedZones`
148
+ array containing one `{ zone, origin: 'verified', verifiedAt,
149
+ observedNameservers, verifiedBy }` entry per delegated zone, plus `updatedAt` and
150
+ `updatedBy`, *before* starting the new build. The first startup then has proof
151
+ already. The advisory startup drift audit re-probes and reports evidence that no
152
+ longer holds; it never corrects the seed automatically, so an operator must
153
+ re-run the seed or use the authority mutation API.
153
154
 
154
155
  The production database is an embedded single-writer `LocalSmartDb` reached over
155
156
  a Unix socket in `os.tmpdir()`, so it is not reachable off-host and the seed must
156
- run on the dcrouter host with dcrouter stopped: stop → seed → start the new
157
- build. `.nogit/debug/seed-dns-authority.ts` does this; it probes every zone over
158
- DoH first and writes nothing on an unproven zone, and it is re-runnable (it
159
- replaces the verified set with what it just proved, so dropping a zone from the
160
- list drops it from authority).
157
+ run on the dcrouter host while no dcrouter process owns it. The complete first
158
+ 18.8.0 sequence is:
159
+
160
+ 1. Stop 18.7.1, suppress automatic restart, and prove no dcrouter process remains.
161
+ 2. Take and verify an offline backup of the complete runtime tree and configured
162
+ data root (normally `~/.serve.zone/dcrouter`).
163
+ 3. Seed the authority singleton while dcrouter remains stopped.
164
+ 4. Start exactly one 18.8.0 runtime and verify its 18.8.0 migration ledger,
165
+ sparse DNS ownership index, authority drift, persisted data, and direct NS,
166
+ SOA, and AAAA answers.
167
+ 5. Restore automatic service management only after those checks pass.
168
+
169
+ `.nogit/debug/seed-dns-authority.ts` is the deployment-local convenience tool:
170
+ it probes every zone over DoH first and writes nothing on an unproven zone, and
171
+ it is re-runnable (it replaces the verified set with what it just proved, so
172
+ dropping a zone from the list drops it from authority).
173
+
174
+ SmartDB 2 must never open a data root that SmartDB 5 has written. Rollback is
175
+ therefore restore-only: stop 18.8.0, restore the exact pre-upgrade runtime tree
176
+ and offline data backup, and only then start 18.7.1.
161
177
 
162
178
  That script lives under `.nogit/` and is therefore **not committed** — it is a
163
179
  one-off operational tool for this specific upgrade, not product code, and it is
164
180
  deliberately not a smartmigration step (see the next section for why). So it
165
- cannot be the only record of the step: the changelog spells out the document
166
- shape (`settingsId: 'dns-authority-settings'`, one
167
- `{ zone, origin: 'verified', verifiedAt, observedNameservers, verifiedBy }` per
168
- zone) inline, and the same result is reachable through `verifyDnsAuthorityZone`
169
- after startup with no restart. The script is a convenience, not a dependency.
181
+ cannot be the only record of the step: the changelog spells out the complete
182
+ document shape, including the `verifiedZones` array, and the same result is
183
+ reachable through `verifyDnsAuthorityZone` after startup with no restart. The
184
+ script is a convenience, not a dependency.
170
185
 
171
186
  **If a zone was missed** — claim it after startup, no restart needed. Order:
172
187
 
@@ -221,22 +236,18 @@ on it, so dual-stack clients see a degraded site. Registering handlers for a
221
236
  verified zone without moving this set would have reproduced the identical defect
222
237
  for every runtime-verified zone.
223
238
 
224
- **How it is reconciled without a restart.** `DnsServerRuntime` owns the array
225
- instance it passes as `authoritativeZones`. smartdns keeps the options object by
226
- reference and re-reads the array on every query, so `syncAuthorityZones()`
227
- mutates it in place and a live server changes its authority set. The alternative
228
- is re-creating the `DnsServer`, i.e. dropping UDP/TCP 53 — the outage this path
229
- exists to remove. `assertLiveAuthorityZones()` checks the reference is still
230
- shared right after construction and logs at `error` if it is not, so a future
231
- smartdns that copies the array fails loudly instead of silently freezing
232
- authority at boot; a test asserts the identity against the installed smartdns.
239
+ **How it is reconciled without a restart.** SmartDNS 8.2 copies constructor
240
+ options and exposes `setAuthoritativeZones()`. `DnsServerRuntime` calls that
241
+ setter on every changed database authority set and updates its local snapshot
242
+ only after the call succeeds. No listener is recreated, so UDP/TCP 53 stays up.
233
243
 
234
244
  `dnssecZone` cannot be reconciled the same way — it is baked into the Rust config
235
245
  at start — so it is taken from the boot-time set, which is **sorted** and
236
246
  therefore stable across restarts rather than dependent on database insertion
237
- order. When the set is empty it becomes `no-authority.invalid`: smartdns falls
238
- back to `[dnssecZone]` when `authoritativeZones` is empty, and `.invalid` is
239
- reserved by RFC 2606, so an unseeded database provably cannot claim a real name.
247
+ order. When the set is empty the required keying zone becomes
248
+ `no-authority.invalid`. An explicit empty `authoritativeZones` array in SmartDNS
249
+ 8.2 claims no zone and does not fall back to `dnssecZone`; `.invalid` remains a
250
+ safe DNSSEC keying sentinel.
240
251
 
241
252
  ### Why removing `dnsScopes` needs no smartmigration step
242
253
 
@@ -250,8 +261,8 @@ worth stating so its absence does not read as an oversight:
250
261
  carried `'bootstrap'` — `persist()` only ever wrote `verifiedZones`, and
251
262
  `verifyZone()` is the only thing that builds an entry. The bootstrap entries
252
263
  existed solely as a read-time projection inside `getSettings()`.
253
- Stronger still: `DnsAuthorityDoc` was introduced in `0d82361`, after the newest
254
- tag, so no released build has ever created the collection.
264
+ `DnsAuthorityDoc` already shipped in 18.7.1, and this change does not alter its
265
+ persisted shape.
255
266
  - **Seeding from `dnsScopes` would forge the proof.** A migration step *could*
256
267
  read it — `createMigrationRunner` runs in-process at startup and already takes
257
268
  deployment-derived seeds — but a `dnsScopes` entry carries no delegation
@@ -266,50 +277,35 @@ same upgrade would claim different zones on different runs, and a migration does
266
277
  not re-run to correct itself. Nondeterministic authority is worse than no
267
278
  automation.
268
279
 
269
- The upgrade path is instead **seed, then start** (above), backed by three
270
- independent loud signals if it is skipped: `DnsAuthorityManager.start()` logs at
271
- `error` on an empty set, `DnsServerRuntime.setup()` logs at `error`, and the
272
- startup drift audit enumerates precisely the zones to claim. Every one of them is
273
- remediable through `dns-authority:write` without a restart, which is the property
274
- that makes a missed seed recoverable rather than an outage.
280
+ The upgrade path is instead **stop, back up, seed, start, verify** (above), backed
281
+ by three independent loud signals if seeding is skipped:
282
+ `DnsAuthorityManager.start()` logs at `error` on an empty set,
283
+ `DnsServerRuntime.setup()` logs at `error`, and the startup drift audit enumerates
284
+ precisely the zones to claim. Every one of them is remediable through
285
+ `dns-authority:write` without a restart, which makes a missed seed recoverable
286
+ without weakening the proof model.
275
287
 
276
- ### Sequenced behind an unreleased smartdns
288
+ ### SmartDNS 8.2 integration
277
289
 
278
- Three items cannot be written against the installed smartdns (7.12.1) and must
279
- land in the same change that takes the bump. All are recorded at their code
280
- sites so they cannot be lost.
290
+ The upstream authority and lifecycle APIs are consumed directly:
281
291
 
282
292
  1. **The private-route overlay must declare itself non-authoritative.** It
283
293
  registers `*` for `A` (`registerPrivateRouteHandler`). The hardened smartdns
284
294
  authority model *suppresses* a default-`authoritative` handler for any name
285
295
  outside every configured zone, so overlay hostnames proven by `provider-zone`
286
296
  — owned, but not delegation-verified, therefore not in `authoritativeZones` —
287
- would silently stop being answered. It needs
288
- `{ authority: 'non-authoritative', owner: 'private-route-overlay' }`, which
289
- is a fourth `registerHandler` parameter that does not exist yet.
290
-
291
- 2. **A supported way to re-zone a running server.** `DnsServerRuntime` currently
292
- keeps the array it passed as `authoritativeZones` and mutates it in place,
293
- then verifies through a cast that smartdns still holds the same reference.
294
- That is a consumer-side shim on a dependency's storage behaviour, and it is
295
- named as one: `authoritativeZones` is a public option, but "the array is kept
296
- by reference and re-read per query" is not a documented contract. It is used
297
- because the alternative — re-creating the `DnsServer` to change its zones —
298
- drops UDP/TCP 53, which is the outage the whole path exists to remove, and
299
- because a `setAuthoritativeZones()` cannot be added without releasing
300
- smartdns first. Guarded two ways in the meantime:
301
- `assertLiveAuthorityZones()` logs at `error` at startup if the reference is
302
- no longer shared, and a test asserts the identity against the installed
303
- smartdns so a copying release fails the suite rather than production. Replace
304
- both with the supported setter when it ships.
305
-
306
- 3. **Per-registration teardown.** `unregisterHandler(pattern, types)` is coarse:
307
- two owners on the identical pattern and type cannot be separated. Nothing
308
- does that today — `DnsManager` is the single owner of apex NS, and
309
- `DnsServerRuntime`'s glue records use different patterns — but the registry
310
- in `DnsManager` exists to work around exactly this. `unregisterHandlerById` /
311
- `unregisterHandlersByOwner` and the handle returned by `registerHandler` are
312
- the released fix.
297
+ would silently stop being answered. It registers with
298
+ `{ authority: 'non-authoritative', owner: 'private-route-overlay' }`.
299
+
300
+ 2. **A supported way to re-zone a running server.** `DnsServerRuntime` calls
301
+ `setAuthoritativeZones()` and never depends on constructor-array identity.
302
+ Setter failure leaves the prior local snapshot intact so authority rollback
303
+ can reconcile against the last successfully applied state.
304
+
305
+ 3. **Per-registration teardown.** `DnsManager` retains the handle returned by
306
+ every `registerHandler()` call. RRset refresh, authority revocation, domain
307
+ deletion, shutdown, and server replacement invoke those handles, so another
308
+ owner's identical pattern/type registration survives.
313
309
 
314
310
  ### Generated apex NS has exactly one owner
315
311
 
@@ -321,9 +317,10 @@ unrelated restart. `DnsManager.registerAuthoritativeZoneDefaults()` is now the
321
317
  only source, for every authoritative zone, reconciled in-process both ways.
322
318
 
323
319
  Consequence worth knowing: generated apex NS hangs off a dcrouter-hosted
324
- `DomainDoc`, so a verified zone without one serves nothing at all. That is
325
- reported as `verified-but-unhosted` drift, because it is otherwise invisible —
326
- such a zone neither REFUSES nor answers.
320
+ `DomainDoc`. A verified zone without one still claims authority and returns its
321
+ configured SOA plus authoritative NODATA/NXDOMAIN, but serves no persisted records
322
+ or generated apex NS. That lame, effectively empty zone is reported as
323
+ `verified-but-unhosted` drift.
327
324
 
328
325
  ## Domain ownership gates certificates and authoritative DNS (2026-07-25)
329
326
 
@@ -368,12 +365,11 @@ synthetic NS/SOA is installed only after activation, never on intent. That work
368
365
  needs a persisted status field, a release-version-matched `ts_migrations` step,
369
366
  new ops API methods, and UI — it is a feature program, not a defect fix.
370
367
 
371
- Why this matters: **smartdns marks any answer a registered handler produces as
372
- authoritative (`aa`) regardless of `authoritativeZones`.** See
373
- `classes.dnsserver.js`: a handler hit sends `'answer'`, a miss inside an
374
- authoritative zone sends `'authoritativeNegative'`, and a miss outside sends
375
- `'refused'`. So registering *any* handler for a zone is a public claim of
376
- authority over it, and dcrouter listens on a public UDP/TCP 53.
368
+ Why this matters: SmartDNS now derives authority solely from
369
+ `authoritativeZones`; default-authority handler matches outside that set are
370
+ suppressed. dcrouter still gates handler registration on the same ownership
371
+ proof so a future authority change cannot activate stale or self-asserted data,
372
+ and the non-authoritative private overlay remains explicitly attributable.
377
373
 
378
374
  Enforcement points:
379
375
 
@@ -392,6 +388,24 @@ Enforcement points:
392
388
  - `certProvisionFunction` and `reprovisionCertificateDomain` — refuse before
393
389
  starting an ACME order that cannot succeed.
394
390
 
391
+ ## DoH path matching happens after TLS termination (2026-08-23)
392
+
393
+ SmartProxy cannot match an HTTP path while the incoming stream is still TLS
394
+ ciphertext. dcrouter therefore persists one nameserver-domain/port socket route
395
+ with `tls.mode: 'terminate'` and no `match.path`, then hands the cleartext stream
396
+ to SmartDNS `handleHttpSocket()`. SmartDNS performs the RFC 8484 dispatch for
397
+ both canonical `/dns-query` and the already-shipped `/resolve` alias. Keeping two
398
+ path-scoped socket routes would make the route decision before either path exists.
399
+
400
+ ## DNS source transitions commit before runtime state (2026-08-23)
401
+
402
+ A provider-to-dcrouter migration is one SmartData session transaction: re-read
403
+ the `DomainDoc` and all `DnsRecordDoc` rows in the session, convert every record,
404
+ remove provider-only metadata, then convert the domain. Any record or domain save
405
+ failure aborts the whole transition. Runtime RRset/default handlers are rebuilt
406
+ only after commit, so a failed transaction cannot report success or expose a
407
+ partially local zone.
408
+
395
409
  ## ACME retry budgets are three independent mechanisms (2026-07-25)
396
410
 
397
411
  Do not assume they share semantics, caps, or re-arm behaviour:
@@ -1182,4 +1196,4 @@ const dcRouter = new DcRouter({
1182
1196
  - `ts/cache/classes.cachedb.ts` - CacheDb singleton wrapper
1183
1197
  - `ts/cache/classes.cached.document.ts` - Base class with TTL support
1184
1198
  - `ts/cache/classes.cache.cleaner.ts` - Periodic cleanup service
1185
- - `ts/cache/documents/*.ts` - Document class definitions
1199
+ - `ts/cache/documents/*.ts` - Document class definitions
package/readme.md CHANGED
@@ -139,7 +139,7 @@ Important runtime behavior:
139
139
  - `dbConfig.enabled` defaults to enabled. Without `mongoDbUrl`, dcrouter uses embedded LocalSmartDb.
140
140
  - If the DB is disabled, constructor-defined proxy traffic can still run, but persistent API routes, tokens, managed domains, and stored certificate state are unavailable. The embedded DNS server is also skipped entirely, because DNS authority is delegation-verified database state — the DoH routes are still generated, but nothing answers behind them.
141
141
  - Qualifying HTTPS forward routes on port `443` are HTTP/3-augmented unless `http3.enabled === false` or the route opts out.
142
- - DNS-over-HTTPS routes are generated on the first `dnsNsDomains` entry at `/dns-query` and `/resolve`.
142
+ - One TLS-terminating DNS-over-HTTPS socket route is generated on the first `dnsNsDomains` entry. SmartDNS accepts RFC 8484 requests on `/dns-query` and the compatibility `/resolve` alias after TLS termination.
143
143
  - Email listener ports can be remapped internally, for example public `25`, `587`, and `465` to unprivileged internal ports.
144
144
  - `emailOutboundMode: 'remoteIngress'` requires an enabled RemoteIngress hub and an eligible connected QUIC edge with SMTP egress enabled. If no eligible edge is available, outbound delivery fails or defers instead of silently falling back to direct SMTP from the hub.
145
145
 
@@ -214,11 +214,13 @@ the running DNS server's authoritative zone set, its generated apex NS records,
214
214
  route certificate warnings, and the private-route overlay together.
215
215
 
216
216
  **Cold start.** A database with no authority document is a legitimate state, and
217
- it means dcrouter is authoritative for nothing and REFUSES every query. The DNS
218
- server still starts — so DoH keeps serving and a zone verified a moment later
219
- takes effect immediately — and the condition is logged at `error` alongside a
220
- startup drift audit listing every dcrouter-hosted zone delegated to us that is
221
- not being served.
217
+ it means dcrouter claims no configured zone and REFUSES ordinary unhandled
218
+ queries. RFC 6761 `localhost` answers and the explicitly non-authoritative
219
+ private-route overlay remain deliberate exceptions. The DNS server still starts
220
+ — so DoH keeps serving and a zone verified a moment later takes effect
221
+ immediately — and the condition is logged at `error` alongside a startup drift
222
+ audit listing every dcrouter-hosted zone delegated to us that is not being
223
+ served.
222
224
  An authority document that cannot be *read* is treated differently: the set is
223
225
  unknown rather than empty, so the DNS services fail and retry instead of quietly
224
226
  revoking every zone. dcrouter itself still comes up — both services are optional
@@ -12,6 +12,19 @@ obtain one at <https://mozilla.org/MPL/2.0/>.
12
12
 
13
13
  Source: <https://github.com/web-push-libs/web-push>
14
14
 
15
+ ## mediabunny
16
+
17
+ `mediabunny` 1.54.0 is used transitively through
18
+ `@design.estate/dees-wcctools` under the Mozilla Public License 2.0.
19
+
20
+ Author: Vanilagy
21
+
22
+ This Source Code Form is subject to the terms of the Mozilla Public License,
23
+ v. 2.0. If a copy of the MPL was not distributed with this file, you can
24
+ obtain one at <https://mozilla.org/MPL/2.0/>.
25
+
26
+ Source: <https://github.com/Vanilagy/mediabunny/tree/v1.54.0>
27
+
15
28
  ## http_ece
16
29
 
17
30
  `http_ece` is used transitively through `web-push`.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '18.7.1',
6
+ version: '18.8.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -966,11 +966,6 @@ export class DcRouter {
966
966
  await this.dnsServerRuntime.setup();
967
967
  })
968
968
  .withStop(async () => {
969
- if (this.dnsServer) {
970
- this.dnsServer.removeAllListeners();
971
- await this.dnsServer.stop();
972
- this.dnsServer = undefined;
973
- }
974
969
  await this.dnsServerRuntime.stop();
975
970
  })
976
971
  .withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
@@ -1785,36 +1780,21 @@ export class DcRouter {
1785
1780
  }
1786
1781
 
1787
1782
  const includeSocketHandler = options?.includeSocketHandler !== false;
1788
- const dnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
1789
-
1790
- // Create routes for DNS-over-HTTPS paths
1791
- const dohPaths = ['/dns-query', '/resolve'];
1792
-
1793
- // Use the first nameserver domain for DoH routes
1794
1783
  const primaryNameserver = this.options.dnsNsDomains[0];
1795
-
1796
- for (const path of dohPaths) {
1797
- const dohRoute: plugins.smartproxy.IRouteConfig = {
1798
- name: `dns-over-https-${path.replace('/', '')}`,
1799
- match: {
1800
- ports: [443], // HTTPS port for DoH
1801
- domains: [primaryNameserver],
1802
- path: path
1803
- },
1804
- action: includeSocketHandler
1805
- ? {
1806
- type: 'socket-handler' as any,
1807
- socketHandler: this.dnsServerRuntime.createSocketHandler()
1808
- } as any
1809
- : {
1810
- type: 'socket-handler' as any,
1811
- } as any
1812
- };
1813
-
1814
- dnsRoutes.push(dohRoute);
1815
- }
1816
-
1817
- return dnsRoutes;
1784
+ return [{
1785
+ name: 'dns-over-https-dns-query',
1786
+ match: {
1787
+ ports: [443],
1788
+ domains: [primaryNameserver],
1789
+ },
1790
+ action: {
1791
+ type: 'socket-handler',
1792
+ tls: { mode: 'terminate', certificate: 'auto' },
1793
+ ...(includeSocketHandler
1794
+ ? { socketHandler: this.dnsServerRuntime.createSocketHandler() }
1795
+ : {}),
1796
+ },
1797
+ }];
1818
1798
  }
1819
1799
 
1820
1800
  /**
@@ -1895,6 +1875,10 @@ export class DcRouter {
1895
1875
  // non-cancelling per-service timeout. Drain it before dependency teardown.
1896
1876
  await this.mailDnsSync.stop();
1897
1877
 
1878
+ // DNS process termination must be confirmed before ServiceManager's
1879
+ // non-cancelling timeout can return control to the CLI's process exit.
1880
+ await this.dnsServerRuntime.stop();
1881
+
1898
1882
  // ServiceManager handles reverse-dependency-ordered shutdown
1899
1883
  await this.serviceManager.stop();
1900
1884