-
DcRouter v34.5.0
StableRelease / build-and-release (push) Successful in 15m27sreleased this
2026-11-11 11:47:23 +00:00 | 0 commits to main since this release2026-11-11 - 34.5.0
Features
- Request headers on gateway-client routes (gateway clients, ops API).
syncGatewayClientRouteacceptsroute.action.requestHeaders(@serve.zone/interfaces32.75.0): header fields dcrouter sets on every request and WebSocket upgrade the route forwards, validated by the sharedvalidateGatewayRouteRequestHeaders(lowercase names, no field dcrouter owns on the backend leg such ashost, hop-by-hop fields,x-forwarded-*,proxy-*orsec-websocket-*, at most 16 fields; a refusal names fields, never a value) and stored as the SmartProxy route'sheaders.request, which replaces every copy of a name the client sent, on WebSocket upgrades too (@push.rocks/smartproxy27.22.1). A gateway client hands its backend a trusted-proxy secret this way. A route withroute.action.tls.mode: 'passthrough'orroute.match.transport: 'udp'is refused with request headers: SmartProxy forwards a passthrough TLS stream unread, and a UDP-only route's traffic never reaches its HTTP service, so they would never be set. The values are write-only: each sync states the complete set (a sync without it removes them),getGatewayClientRouteslists onlyrequestHeaderNames, and every route listing (RouteConfigManager.getMergedRoutes: opsgetMergedRoutes, the Ops UI, MCP, gateway reads) takesheaders.requestoff a gateway-client route and lists the names inIMergedRoute.requestHeaderNames; operator routes are listed as before. The stored route keeps the values, so a failed sync's rollback restores them.getGatewayCapabilitiesanswersroutes.requestHeaders: true. The values sit in the route database as SmartProxy applies them. - Source profile names in gateway-client route reads (gateway clients).
getGatewayClientRoutesanswerssourceProfileNamenext tosourceProfileRef(@serve.zone/interfaces32.75.0), both from the same source binding: the profile name dcrouter recorded when the route was synced, so a client reads the binding back by the name it synced. A binding stored without a name answers the id alone. - Data version 34.5.0 and its way back (migrations, CLI). The
gateway-route-request-headersstep changes nothing on the way up; dcrouter 33.0.0 through 34.4.0 refuse to start on its ledger.dcrouter migrate --to 34.0.0deletes the request headers of every gateway-client route in one transaction with the ledger stamp (operator routes keep theirs) and logs each route and its field names, never a value, so dcrouter 34.0.0 through 34.4.0 never list one. Nothing keeps the values: after upgrading again, each gateway client's next full sync sets them again. From 34.5.0 data,--to 32.6.0runs this step first and then the route DNS ownership restore; each commits on its own, so a refusal of the second leaves the data at 34.0.0.
Maintenance
- Restore sha512 integrity for 22
pnpm-lock.yamlentries (once,wrappy,etagand other old packages) that a Verdaccio resolve had recorded as sha1 in the 34.0.0 tooling update; each value is npmjs's and matches the tarball both registries serve. - Release tooling:
@git.zone/tsrepo1.1.1 → 2.0.0,@git.zone/tsdocker3.13.0 → 3.14.0,@types/node26.6.5 → 26.6.6 and pnpm 12.14.0 → 12.17.0 (12.18.0 does not yet meet the seven-day rule). tsrepo 2.0.0 makes@push.rocks/smartmongoan optional peer. No dcrouter test callscreateSmartmongo(), so smartmongo,@lossless.org/nosqldband the rest of tsrepo's former subtree are gone from the install.@git.zone/tsdenostays on 1.15.0 until lane D5 of the Deno-removal programme movesbuild:binaryoff it. The runtime dependency versions are unchanged; the only change in the production closure is the type-only@types/node, which resolves to 26.6.6 alone (it was 26.6.2 and 26.6.5). Sevensha1-lockfile entries written by the Verdaccio resolve are restored to npmjs's sha512.
Downloads
- Request headers on gateway-client routes (gateway clients, ops API).
-
DcRouter v34.4.0
StableRelease / build-and-release (push) Successful in 17m36sreleased this
2026-10-31 18:12:32 +00:00 | 4 commits to main since this release2026-10-31 - 34.4.0
Features
- Add an opt-in loopback-only Socket8 workload mail listener at
/mail/typedrequest/v8, exposing only credential-scoped enqueue and own-spool status through the existing durable WorkApp mail manager (AGL-1220). No Ops router, admin fallback or ingress/DNS rule is added. Disabled until explicitly configured and routed by the operator.
Fixes
- Adopt compatible SmartMail 2.2.4, SmartNetwork 4.10.2 and SmartACME 9.8.2 owner maintenance releases while retaining the SDK, transport and storage contracts. Remove the legacy acme-client/Axios/follow-redirects production chains. A temporary project guard admits only the owner-approved node-forge 1.4.2 artifact by exact SHA-512; unrelated age/severity gates and global manager configuration remain unchanged (AGL-1219/AGL-1220).
Maintenance
-
Prepare the final Hub container runtime on reviewed base8.0.2 roots and Node24.22.1/ABI137; retain build tooling and all application/native packages, remove complete unused final package-manager components/caches, and verify executing Node/license bytes and absent launchers (AGL-1230). Native packaged startup and final-image scans remain separate acceptance gates.
-
Record main-only branch pushes and evidence-backed AGL board updates in repository guidance; qualify project-local tsdocker 3.13.0 and canonical lockfile formatting without changing the production graph (AGL-1217/AGL-1220).
Downloads
- Add an opt-in loopback-only Socket8 workload mail listener at
-
DcRouter v34.3.0
StableRelease / build-and-release (push) Successful in 16m44sreleased this
2026-10-26 10:47:38 +00:00 | 12 commits to main since this release2026-10-26 - 34.3.0
Features
- A health list of stored DNS records dcrouter cannot encode (ops API, AGL-1008).
getInvalidDnsRecords(dns-records:read) reads everyDnsRecordDocin every domain and returns{ records: IDnsInvalidRecord[] }, sorted by domain, name and id: each entry names the record (id,domainId,name,type), the storedvalueas it is, the refusedfield(TDnsRecordValueField:type,value,address,target,preference,exchange, or an SOA field) and thereason. A record of a dcrouter-hosted zone in the list is not served; a provider zone record in it is a local mirror that a write or a migration refuses. dcrouter never rewrites them: fix or delete each one. - Refused record values are typed (ops API).
createDnsRecordandupdateDnsRecordanswer a refused value withsuccess: false, a message naming the record and the field, andinvalidValue: IDnsRecordValueRefusal(idfor an existing record,name,type,field,reason);migrateDomainanswers a refused zone withinvalidRecords. Nothing is written or journaled for such a refusal; for automation it is a non-retryablerefusal: 'invalid-value'. - Hand one operator record back to route DNS (ops API, AGL-1009).
handBackDnsRecordToRouteDns(admin,dns-records:write;recordId,gatewayClientId,reason) makes an unowned A or AAAA record route-owned for that client's claim at its hostname, keeping its DNS data, and writes anassignentry to the DNS mutation journal (new operation,DnsManager.assignRecordManagement: a compare-and-set on the record still being the unowned record that was checked; every automation actor is refusednot-owned). It is refused with a typedrefusaland nothing written (record-not-found,not-operator-record,not-address-record,protected-hostname,hostname-held,route-inventory-unavailable,no-active-claim,target-unavailable,value-mismatch,zone-mismatch,other-records,ownership-changed,mutation-failed, withretryable) unless the record is the hostname's only A, AAAA or CNAME record and its type, value and declared proxy state equal route DNS's target for that claim. Route DNS still never adopts an operator record on its own,createdByincluded. - Route DNS's desired target per claimed hostname, read only (ops API, AGL-1009).
getGatewayRouteDnsTargets(dns-records:read, optionalhostname) answers{ targets: IGatewayRouteDnsTarget[] }: per hostname and claiming gateway client the claim owner id, the route external keys,recordTypeandtargetIp(absent when route DNS has no usable target) and the declaredproxiedstate, from the reconciler's own rule; a retryableroute-inventory-unavailableerror until the route inventory is loaded.
Fixes
- One owner for the meaning of a DNS record value (DNS, AGL-1008).
ts/dns/record-value.tsparses a value into its RDATA fields or refuses it naming the field, and every path uses it: the embedded server registration of stored and configured records,createRecord,updateRecord, the Cloudflare write and import composition, the provider migration plan and its value comparison,migrateDomain, and the mail DNS MX check. Fields are split on runs of spaces and tabs the same way on write and on load. MX is<0-65535> <exchange>, with the RFC 7505 null MX0 .; SOA is two names and five numbers from 0 to 4294967295; A is a dotted IPv4 address; AAAA is exactly what smartdns 11.1.0encode_aaaaencodes: eight groups of 1 to 4 hex digits, or at most one::that fills them up to eight (also next to eight written groups), with no mixed IPv4 tail (::ffff:192.0.2.1) and no zone id, which smartdns does not encode; CNAME and NS are one name. A name may end with a dot and has no empty label, no label over 63 octets and no more than 253 octets; its characters are not restricted. TXT is taken as it is, CAA as non-empty text. Any other type (SRV included) is refused by its type. Up to 34.2.0 a write stored any value string in a dcrouter-hosted zone, and the runtime split an MX on one space: an MX stored as a bare exchange became{ preference: NaN, exchange: undefined }, smartdns refused it, and the whole RRset answered SERVFAIL. updateRecordchecks the whole record as it would be after the update (DNS): a new value against the stored type, a new type against the stored value, and a stored bad value on an update that does not change it.- A stored record the runtime cannot encode is not served, and is named (DNS): at start and on every RRset refresh it is left out with one
errorlog line per record id, and the rest of its RRset answers NOERROR (an RRset of only such records is NODATA), never SERVFAIL for all of it. It is ingetInvalidDnsRecords. A record with TTL 1 still loads and answers with TTL 1. The startup line counts only the records it registered and names the refused ones apart (registered N dcrouter-hosted DNS record(s) from DB; not serving M record(s) …); up to 34.2.0 it counted every stored row. - The Cloudflare import refuses a record value the shared owner refuses, for every type (DNS); up to 34.2.0 it checked only the MX shape. It never refuses a value smartdns encodes. One refused record fails the whole listing of its zone, naming the record (
… has invalid <type> <field> for <name> (record <provider id>): <reason>), and with it everything that lists that zone at Cloudflare:syncDomainanswersProvider list failed: …, a domain import keeps the domain without its records (awarnline), a migration into that zone, ACME DNS-01 challenge checks and testing DNS writes there fail. Fix the record at Cloudflare. - The mail DNS MX check uses the shared owner (email, AGL-1008). An observed null MX
0 .and an exchange with characters outside[a-z0-9-](such as_) are nowconflict(an unexpected value) instead ofinvalid; a value the owner refuses (a bare exchange) staysinvalid. migrateDomainrefuses, in both directions, a zone with a record whose value dcrouter cannot encode, naming each one, before it reads or writes the target (DNS); the provider migration plan refuses such a record as a conflict by name, and the provider-to-dcrouter commit checks the records it re-reads in its transaction again.
Downloads
- A health list of stored DNS records dcrouter cannot encode (ops API, AGL-1008).
-
DcRouter v34.2.0
StableRelease / build-and-release (push) Successful in 15m22sreleased this
2026-10-26 02:04:30 +00:00 | 17 commits to main since this release2026-10-26 - 34.2.0
Features
- A supported way back from the route DNS ownership release (migrations, AGL-894). The
release-annexed-gateway-route-dns-recordsstep (data version 34.0.0) now records every release in the migration-ownedMigrationUndoRecordcollection: one row per released record with its identity, its cause (adopted, or replaced with the platform event id) and exactly the ownership fields it held, under a header the step setscomplete. It runs as a smartmigration.transactional()step: the rows, the$unsets, the header and the ledger stamp commit in one transaction or not at all (the header is created just before it with$setOnInsert, since SmartDB creates no collection inside a transaction, and is never turned back torecording). The step has a.down()that restores the recorded ownership in one transaction with the stamp back to 32.6.0: it judges every row first, skips (and logs) a record deleted since the release, restores an unowned record (nomanagedBy,managedOwnerIdormanagedRecordKey) even when its value or TTL changed since, counts a record owned exactly as recorded as already restored, and refuses the whole downgrade, naming every conflict and writing nothing, when a record is owned otherwise or another record holds a recordedmanagedRecordKey. Each restore is a$setconditional on the record still being unowned that must match one record, and the header becomesrevertedin the same transaction. Records 34.x wrote after the upgrade are kept, and so are the 34.x collectionsDnsMutationJournalDocandDnsProtectedHostnameDoc(33.0.0 reads neither; the downgrade logs how many protected hostnames it kept). Both directions refuse, before any write, work over SmartDB 5.7.1's transaction cap of 10,000 documents and 32 MiB (the whole ofDnsRecordDoc,MigrationUndoRecordandSmartdataEasyStoreis in the transaction's snapshot) and never split it into batches. A re-upgrade after a downgrade replaces the recorded rows. A database whose ledger never ran the step (stamped past it) has nothing to restore; a fresh install runs the step like every other and records an empty release. dcrouter migrate --storage-path <absolute path> --to <version> [--db-name dcrouter] [--plan]walks the data of a stopped dcrouter back (CLI). It first checks the root offline (LocalSmartDb.inspectOfflinePhysicalNamespaces, which fails while an engine holds the root, and needs the database to exist there), then starts the engine, whose owner lock refuses a root a running dcrouter owns, and runs the newcreateDowngradeMigrationRunner. That runner registers the whole chain, so smartmigration refuses a target that is not a step boundary and a path across a step without.down()before any handler runs; it refuses by name a database without a ledger, a ledger a newer dcrouter wrote and a target ahead of the ledger (forward migrations happen when dcrouter starts). A target equal to the ledger is a no-op.--planreports the steps without writing anything. The output ends with oneDCROUTER_MIGRATE_RESULT:{json}line (ok, the versions and the steps, or the error); the exit code is 0, 1 on a refusal or failure, and 2 on a command line it does not accept.- A database that dcrouter 34.0.0 or 34.1.0 upgraded is refused by the downgrade (migrations). Those releases ran
release-annexed-gateway-route-dns-recordswithout recording what they released, so there is nothing to restore ownership from:dcrouter migraterefuses it by name, naming the step and the cause, with nothing changed, and the way back for it stays the export taken before the upgrade to 34.0.0. That includes a database 34.0.0 or 34.1.0 created: a fresh install'sfresh-install-versionrun applies every step from 17.7.2 on, this one included. The migration ledger decides whether the step ran (an entry for it, whatever its status), and the header and that entry together decide between restoring, nothing to restore and a refusal (the table ints_migrations/readme.md). Upgrade a 33.0.0 database straight to 34.2.0 or later to keep the way back. The same refusal applies when the step's header is stillrecordingor was reverted and the step applied again since by one of those releases.
Fixes
runClirefuses an argument it does not know instead of starting the router (CLI). Up to 34.1.0 any unknown argument,dcrouter migrate …included, started dcrouter against the storage it was meant for. Only a baredcrouterstarts it now; anything else but--versionorversion,--help,-horhelp, andmigrateexits with code 2 and the usage, and nothing is started.
Maintenance
@push.rocks/smartmigration2.0.1 → 2.1.0 (exact pin, the maintenance/2 line; 4.x needs@lossless.org/client2, and dcrouter stays on 1.2.0), for.transactional()steps and the in-progress marker: every other dcrouter step now writesinProgress: { stepId, direction, … }to the ledger before its handler runs and clears it with its stamp, the next run finishes an interrupted step in the same direction and refuses the opposite one (INTERRUPTED_STEP_OPPOSITE_DIRECTION,INTERRUPTED_STEP_NOT_PLANNED). Rollback limit: a runner below smartmigration 2.1.0 (dcrouter 34.1.0 and earlier) drops the marker on its next ledger write, so finish or abandon an interrupted step before rolling back to one of those releases.bson7.3.2 is a direct dependency (the version the driver and SmartDB already resolve), for the transaction cap check's document sizes.@serve.zone/dcrouter-v33(npm:@serve.zone/dcrouter@33.0.0, exact) is a new test-only dev dependency: the downgrade tests seed a root with the published 33.0.0 and read it back with it in a separate process.
Downloads
- A supported way back from the route DNS ownership release (migrations, AGL-894). The