-
DcRouter v32.1.2
StableRelease / build-and-release (push) Successful in 25m28sreleased this
2026-09-19 08:05:04 +00:00 | 70 commits to main since this release2026-09-19 - 32.1.2
Fixes
-
keep the engineering notes (
hints.md) out of the published image (docker):.dockerignorenow excludes it, the way it already excludesreadme.plan.mdand the test tree. -
retire the last three raw-driver sites in
ts/(db, email, monitoring):@lossless.org/clientmoves to1.1.0exactly, whose document model adds the four capabilities these sites were waiting for. No MongoDB driver handle remains ints/;ts_migrations/is the only place in the repository that holds one.OpsConfigEventDoc.upsertOpenEvent()usesatomicUpdate(..., { upsert: true })anchored on the sparse uniqueopenDedupeKey, seeding the@unI()identity through$setOnInsert. The client refuses an identity seed withoutupsert: true, refuses$seton an identity at all, and refuses a seed that contradicts an identity anchor in the filter, so deduplication keeps exactly the guarantee the raw upsert had: one open occurrence per key, its identity fixed by the insert that won.EmailTrafficBucketDoc.persistAbsolute()usesatomicUpsertMany()— one unordered batch, bounded by the client at 1..1000 operations and 16 MiB, which a 500-bucket flush always fits. Each operation is anchored on the declared uniquebucketStart, uses$maxfor the monotonic counters so a replayed absolute snapshot can never lower one, and seeds the identity through$setOnInsert.SmartMtaStorageDocdeclaresidentityAsDocumentId: 'id', so the content address (sha256(normalizedKey)) that has always been this collection's_idis now the declared identity as well, andSmartMtaStorageManageraddresses every document through it instead of a raw collection handle. This declaration is irreversible: reads are answered through_idfrom here on, so a row whose_idis not its identity would stop resolving. It is safe because dcrouter has only ever written_id === id === sha256(normalizedKey)here. The model declares no unique index onid— the client refuses one for a field that owns the document_id, since the primary key is the only uniqueness authority such a field may have — and every upsert must pin the identity by equality, which the client enforces rather than letting a generated_idcontradict the mapping.SmartMtaStorageManagerno longer takes a database: the document model owns its binding.- One persisted-byte difference, in
SmartMtaStorageDoconly: documents written from here on also carry the ORM's own_createdAt/_updatedAtbookkeeping, which the raw writes did not produce. Every application member (_id,id,key,value,createdAt,updatedAt), the string_idshape,_id === idand the index set (_id_alone) are unchanged, and no dcrouter code reads the two ORM members. They cannot be avoided through any client API —atomicUpdatewrites_updatedAtunconditionally — so existing rows simply lack them until their next write.OpsConfigEventDocandEmailTrafficBucketDocmove no byte at all: member sets, values,ObjectIdprimary ids and index names, keys and options are identical, captured frommainand compared throughinspectStoredDocuments()/inspectNamespace(). - New specs: a concurrent open event coalescing onto one seeded identity and the three identity-seeding refusals (
test.ops-config-events.node.ts), a 500-bucket flush of mixed inserts and regressed updates keeping$maxmonotonic (test.monitoring-events.node.ts), content-addressed lookup, overwrite and delete by identity plus the unpinned-upsert refusal (test.smartmta-storage-manager.node.ts), and the stored envelope of the content-addressed model (test.nosqldb-persisted-bytes.node.ts). The remaining raw index and document reads in those three spec files move toinspectNamespace()/inspectStoredDocuments().
-
route Web Push persistence through the document client (webpush, db) No persisted byte moves and no migration step: the stored member set, the ORM-assigned
ObjectIdprimary id,_createdAt/_updatedAtand every index name, key and option are unchanged, proven bytest/test.webpush-persisted-bytes.node.ts, which compares the stored BSON against the member sets captured from the pre-conversion implementation and the live index topology againstts/webpush/webpush-indexes.ts.WebPushBindingDoc,WebPushAdmissionDocandWebPushSpoolDocdrop theirgetNativeCollection()accessors and declare their full index topology with@namedIndexunder the exact names the releasedweb-push-provider-schemamigration created. Binding a model re-creates any declared index the namespace is missing (a foreground index build on a live collection) and verifies the rest; the migration keeps owning index creation for existing databases, and the declarations exist so the topology is checked, not so application code defines it.- The three models declare no
@unI()identity: the ORM hard-codes an identity field's unique index name to<field>_1, which would add a second unique index over the same key asweb_push_binding_id_unique/web_push_spool_id_unique.atomicDeleteis anchored by the declared unique indexes instead, which the ORM accepts as globally constraining. - All ~80 native operations in
ts/webpush/classes.webpush-manager.tsmove togetInstance/exists/getCursor/getCount/atomicUpdate/atomicUpdateMany/atomicFindOneAndUpdate/atomicDelete/atomicDeleteMany, keeping every compare-and-set fence, lease token, controller generation and bounded batch._updatedAtis no longer hand-written by 15 call sites; the ORM owns it. - The delivery claim's
$ornow carries one branch per claimable state instead ofstate: { $in: [...] }. The ORM requires an equality anchor in every branch of a write filter; the branches match exactly the same documents and still rideweb_push_spool_delivery_scan. - Startup asserts the index topology through the client's collection-topology inspection and requires an exact match: a renamed or altered index fails closed as before, and an index nobody declared now fails closed too, so a hand-added index on
web_push_binding,web_push_admissionorweb_push_spoolstops dcrouter from starting. Remedy: drop the extra index (db.<collection>.dropIndex('<name>')) or add it to the migration and the model declaration. A declared index that is missing is re-created on bind, not refused. - Fixes a defect introduced with 32.1.1: the binding, spool and admission insert races tested the MongoDB driver's
code === 11000, but the client reports a violated unique index asSmartdataPersistenceError('unique_conflict'), so those races rethrew instead of recovering.
-
route production document access through the client's public API instead of the raw MongoDB driver (db, dns, security, monitoring)
SmartdataDb.mongoDb/SmartdataCollection.mongoDbCollectionare no longer used for ordinary reads and writes ints/.CachedEmail,GatewayClientDoc,OpsConfigEventDoc,EmailTrafficBucketDoc,IpIntelligenceDocandDnsManager.migrateToDcrouternow usegetCursor,getPagedInstances,getCount,atomicUpdate,atomicUpdateMany,atomicDeleteandatomicDeleteMany. Every fence is preserved: the gateway-client compare-and-set still matches exactly one revision, plural deletes anchor on the scanned identities and re-evaluate their range guard server-side, and the DNS provider-link removal stays inside the migration transaction.IpIntelligenceDoc.findOldestIds()/deleteByDocumentIds()becomefindOldestIpAddresses()/deleteByIpAddresses(): retention now addresses records by their immutableipAddressidentity instead of opaque_idvalues.SecurityPolicyManagerretention is unchanged in behaviour, batch sizes and ordering.- Three sites kept a raw handle with a
WHYcomment naming the@lossless.org/clientcapability they were waiting for:EmailTrafficBucketDoc.persistAbsolute()(needs$max/$min, an identity-seeding$setOnInsertand a plural upsert),OpsConfigEventDoc.upsertOpenEvent()(needs the identity-seeding$setOnInsert) andSmartMtaStorageManager(needs a caller-supplied deterministic_id).@lossless.org/client1.1.0 shipped all four capabilities and the first entry above retires those handles in this same release. The Web Push persistence conversion is released alongside it: no client capability is missing there, but the three models declare no identity or index topology, so converting them changessave()behaviour for live collections. - New specs: bucket retention ordering and batching (
test.monitoring-events.node.ts), IP intelligence oldest-first scanning and identity-anchored deletion (test.security-policy-manager.node.ts), and the ops-config event paging/prune bounds now run against a real database instead of a faked native collection (test.ops-config-events.node.ts).
Downloads
-