-
released this
2026-10-24 08:47:12 +00:00 | 23 commits to main since this release2026-10-24 - 34.0.0
Breaking Changes
- Route DNS no longer takes operator records (DNS). In
reconcilemode the gateway route DNS reconciler adopted an identical unowned A/AAAA record and replaced a differing one (deleting it and creating its own,managed-overwrote-manual); a record taken that way was later deleted with the route, which is howcode.foss.globallost its A record on 2026-10-21 although an operator route still served it. Route DNS now acts only on records it owns: identical unowned records satisfy the claim as they are (manual-satisfied, evidencemanaged: false), and a differing unowned record, any unowned CNAME, or unowned records next to owned ones are a non-retryablerecord-conflictwith adns-conflictplatform event, with no DNS change.manual-satisfiedand the newmanaged-releasedneed the@serve.zone/interfacesrelease that adds them toTGatewayRouteDnsStatus;managed-overwrote-manual,managed-orphan-deletedandcompensation-failedare no longer produced by route DNS. Therelease-annexed-gateway-route-dns-recordsmigration step returns the records route DNS adopted (createdBynotgateway-route-dns) or replaced (named by a "Gateway route DNS automation claimed a manual hostname" event) to operator ownership and logs each one. - Every
DnsManagerrecord mutation takes a requiredIDnsMutationContext(actor,trigger,reason,ownerId?):createRecord,updateRecord,deleteRecord,deleteRecords, and the newreleaseRecordManagementandhandoffRecordManagement. Anautomationactor may change only records its ownmanagedBynamespace manages (refusal: 'not-owned'); anoperatoractor is not bound.updateRecordrefuses a type change on a provider zone.deleteRecordsByNameAndType, unused and blind to ownership, is removed, and so isadoptRecordManagement, whose only caller was the route DNS adoption path. - Downgrade limit (DNS, data). The
release-annexed-gateway-route-dns-recordsmigration step (data version 34.0.0) returns adopted and replaced records to operator ownership; running an older dcrouter does not undo it, so going back means restoring the checkpoint taken before the upgrade.
Fixes
- Plan a domain migration to a DNS provider before its first write (DNS).
migrateDomaincreated each record at the provider one by one, only logged a failed create and still reported success, unregistered the embedded DNS server's handlers record by record inside that loop and switched the domain metadata regardless; withdeleteExistingProviderRecordsit first deleted every record in the target zone, ignoring delete failures. It now reads the complete target zone and refuses, with nothing written, when a record there conflicts with a migrated record (another value of the same name and type, anAbeside a foreignAAAAor the reverse, or a CNAME beside any other record) or when the zone holds an explicit SOA row or apex NS row (an authority record of the dcrouter-hosted zone, to be deleted in dcrouter first; the provider's own apex NS and SOA are left alone, and NS delegations below the apex migrate), adopts identical records, and creates the rest, passing the local record'sproxiedflag to the provider; a failed create or local switch deletes exactly the records the run created and reports failure. When the local switch throws, dcrouter reads the domain back: a switch that landed counts as success, one that did not is rolled back, and if the read fails nothing is deleted and the failure names the created provider records. The records, the domain metadata and the embedded DNS server switch only after every write succeeded. A migration never changes or deletes a target record it did not create, sodeleteExistingProviderRecords: trueis now refused (the field is deprecated) and the Ops UI migration dialog no longer offers it (it defaulted to on); the dialog shows dcrouter's refusal instead of reporting success. - Authorize the DNS mode a gateway-client route sync runs (gateway clients). The capability check used the requested
dnsMode, but deleting a route stored withreconcile, or moving it toskip/observeor another hostname, removes its record inreconcilemode, so a token withoutsyncDnsRecordscould delete a reconciled route's DNS record by sendingdnsMode: 'skip'. The stored mode is now authorized for that cleanup; the Onebox delete call, which sends nodnsMode, is unchanged. - Keep answering a name while its DNS records are rebuilt (DNS). Rebuilding a dcrouter-hosted RRset unregistered its handlers before reading the records, so a query during the read found no handler, and a failed read left the name unanswered until the next change or restart. The records are read first and the handlers swapped in one synchronous step; a failed read keeps the old handlers.
- Keep an address another route still serves (DNS). When a gateway route's claim ends — route deleted, disabled, moved to another hostname, or found without a route by the periodic prune — its records were deleted while only a reconcile route of the same gateway client counted as still using the hostname. While any enabled route on the dcrouter (operator route or gateway-client route in any DNS mode, the claim's own route after a move to
skiporobserveincluded) matches the exact hostname through anymatch.domainsentry, the records now stay in place and their ownership is released to the operator (managed-released, journaled, with a platform event). The periodic prune leaves a record of an ended claim alone while another claim is live at the hostname, so that claim takes the record over in place. - Do nothing while the route inventory is not loaded (DNS).
RouteConfigManageris assigned before it initializes, so a failed initialize left a live manager with no routes and the route DNS prune deleted every route-owned record. Route DNS, the gateway-client hostname manager (record syncs, certificate admissions, orphan sweep), the testing hostname check and gateway-client certificate ownership now readgetReadyMergedRoutes(), which is undefined untilinitialize()finished, and fail closed (zone-unavailable/issuance-unavailable, retryable).getGatewayClientRoutes,getGatewayClientDomainsandgetGatewayClientDnsRecordsanswer a retryable error (errorData.code: 'route-inventory-unavailable') until then instead of an empty list. A prune pass that would withdraw more than two and more than 20 % of the route-owned records withdraws none and records oneGateway route DNS cleanup held: mass withdrawalevent per pass; it counts only the records the prune would withdraw, so the record of an ended claim at a hostname a live claim holds (left for that claim to take over) neither triggers the hold nor appears in its count. - Never leave a hostname without an address while its record changes (DNS). Route DNS and the gateway-client hostname manager deleted the old record before creating its successor, and route DNS restored deleted records by deleting and recreating again. An owned record of the target type is now edited in place, a change between A and AAAA creates the new record before the old one is deleted, an owned CNAME that would have to become an address is a non-retryable
record-conflictwith adns-conflictplatform event (it can neither stay beside the address nor change type without a gap, and no automation writes one, so it is old data for the operator to remove), and an owned provider record without its provider id is refused as retryable instead of being deleted and recreated; the compensation path is removed. A record of a claim that has ended is handed over in place to the claim that now holds the hostname instead of being pruned and recreated. - Release ownership of a provider record that changed identity outside dcrouter (DNS). When provider zone synchronization finds the record behind a managed id renamed or retyped, the record now becomes an operator record in the same single write that mirrors the change (journaled, with a platform event), unless a pending journal intent shows dcrouter's own interrupted mutation made that change.
- Clear a stale proxy flag when a provider stops reporting it (DNS). Provider zone synchronization mirrored a provider record without
proxiedthroughsave(), which only sets the fields a document holds, so the storedproxied: truestayed and the record read as changed and was rewritten on every pass. The mirror rewrite now removes the stored flag in the same write.
Features
- Protected hostnames (DNS, ops API, admin).
getDnsProtectedHostnames,addDnsProtectedHostnameandremoveDnsProtectedHostnamemanage exact hostnames at which no automation (route DNS, gateway-client hostname records, mail DNS, migration to a DNS provider) may create, update, delete, adopt or hand over any record; releasing ownership stays allowed and operator changes are not bound. Nothing is protected by default: after upgrading, protect the hostnames whose records must only change by hand, such ascode.foss.global.getDnsRecordsmarks such recordsprotectedHostname: true, and the Ops UI shows them. - DNS mutation journal (DNS, ops API). Every record mutation dcrouter makes writes its intent before it runs (and does not run if that write fails) and its outcome afterwards, with actor, trigger, reason, owner and the record before and after; refusals, releases, handoffs, protected-list changes and the mirror rewrites and deletions provider zone synchronization observes are journaled too; an observation is written before the mirror write and completed
observedafter it, orfailedwhen the record changed in between. A migration to a provider journals each provider create and rollback delete the same way underprovider-migration, and its record links and domain switch inside the commit transaction; a protected name among its records refuses it before the first write.migrateDomaintakes the requester'sIDnsMutationContext.getDnsMutationJournal(dns-records:read) reads it by hostname, zone and time; entries are kept 180 days. ACME DNS-01 challenge records and testing DNS keep their own durable owners and are not part of it.
Maintenance
- Release tooling: the deprecated single tools
@git.zone/tsbuild6.0.2,@git.zone/tsbundle3.7.1,@git.zone/tsrun3.0.3,@git.zone/tstest8.0.0 and@git.zone/tswatch7.1.1 are replaced by@git.zone/tsrepo1.1.1, following its "Migrating from the single tools" section: thebuild,bundle,testandwatchscripts runtsrepo build,tsrepo bundle,tsrepo testandtsrepo watchwith the same arguments, the tests importtapandexpectfrom@git.zone/tsrepo/tapbundle, the@git.zone/tsbundleand@git.zone/tswatchsections of.smartconfig.jsonmoved unchanged to"@git.zone/tsrepo".bundleand.watch(thedcrouter-devwatcher starts the dev server withtsrepo run), the SmartDB rollback test starts its fixture withtsrepo run, andcli.ts.jsregisterstsx(now a dev dependency, 4.23.15, the version tsrepo uses) and importscli.child.tsinstead of callingtsrun.runPath. pnpm 12.11.2 → 12.14.0 (12.15.0 waits for the seven-day rule);@git.zone/tsdocker3.12.0 and@types/node26.6.5 are current.@git.zone/tsdenostays on 1.15.0 forbuild:binary, whose move to a Node.js single executable application is its own change (lane D5 of the Deno-removal programme). This tooling change moves no runtime dependency version;@push.rocks/smartconsole2.1.0 (through@push.rocks/smartlog4) now resolves its optional peer@push.rocks/smartmarkdownto 3.3.0, which tsrepo brings into the install. The Dockerfile's base image digests are unchanged.
Downloads
- Route DNS no longer takes operator records (DNS). In