Closes the half of #223 that was left undone when it was closed. Reported again by the submitter's colleague at LINET: duplicate checking landed for vCard and never for LDIF, so re-importing an address book still leaves a second copy of everything.
It was left open because I wanted the matching key decided rather than guessed. The decision had already been made on #174 — an attribute that can change is fine, because it will not have changed between two imports minutes apart — and I closed the issue without acting on it.
What it does
uidFromDn derives a namespaced, stable uid from an entry's distinguished name (urn:x-ihasmail:ldif:…), normalised for the case and spacing two exports of one directory differ in. The namespace is what keeps it from being read as a UID a vCard author meant.
A card the target book already holds under that uid is updated, not duplicated — merged as the vCard import merges: properties the file carries win, properties it does not mention are left alone. Skipping would mean a corrected export corrects nothing, which is the whole reason anyone re-imports.
The toast reports created and updated apart, which is the pair that was asked for.
An entry with no usable dn gets an identity of its own and duplicates on re-import, as everything did before.
Three things worth knowing
Matching is per address book. Two customer directories that each hold a cn=John Smith stay two people as long as they are filed separately; imported into one book they merge. That is the one way this can be wrong, and separate books are the escape hatch.
The look-alike count stays and now means something narrower — entries dn matching could not catch: one whose dn moved between exports, and anything imported before there was a dn to match on. Still counted, never merged, because name-plus-email is a guess wrong in both directions.
A file with two entries under one dn is malformed, since a directory cannot hold that, and now becomes one card rather than two sharing an identity.
Tests
New ldif-dedupe.test.ts covers update-not-duplicate, the merge, that the card is not refiled into the import target, per-book scoping, case and spacing, and created/updated reported apart. uidFromDn has unit tests next to the parser. Existing LDIF suites updated where they asserted the old "invents a UID" behaviour. 1034 web tests, 142 server tests, typecheck clean.
Closes the half of #223 that was left undone when it was closed. Reported again by the submitter's colleague at LINET: duplicate checking landed for vCard and never for LDIF, so re-importing an address book still leaves a second copy of everything.
It was left open because I wanted the matching key decided rather than guessed. The decision had already been made on [#174](https://github.com/Coffey-Labs/ihasmail/issues/174#issuecomment-5507056538) — an attribute that *can* change is fine, because it will not have changed between two imports minutes apart — and I closed the issue without acting on it.
## What it does
- `uidFromDn` derives a namespaced, stable uid from an entry's distinguished name (`urn:x-ihasmail:ldif:…`), normalised for the case and spacing two exports of one directory differ in. The namespace is what keeps it from being read as a UID a vCard author meant.
- A card the target book already holds under that uid is **updated, not duplicated** — merged as the vCard import merges: properties the file carries win, properties it does not mention are left alone. Skipping would mean a corrected export corrects nothing, which is the whole reason anyone re-imports.
- The toast reports **created and updated** apart, which is the pair that was asked for.
- An entry with no usable `dn` gets an identity of its own and duplicates on re-import, as everything did before.
## Three things worth knowing
**Matching is per address book.** Two customer directories that each hold a `cn=John Smith` stay two people as long as they are filed separately; imported into one book they merge. That is the one way this can be wrong, and separate books are the escape hatch.
**The look-alike count stays and now means something narrower** — entries `dn` matching could not catch: one whose `dn` moved between exports, and anything imported before there was a `dn` to match on. Still counted, never merged, because name-plus-email is a guess wrong in both directions.
**A file with two entries under one `dn`** is malformed, since a directory cannot hold that, and now becomes one card rather than two sharing an identity.
## Tests
New `ldif-dedupe.test.ts` covers update-not-duplicate, the merge, that the card is not refiled into the import target, per-book scoping, case and spacing, and created/updated reported apart. `uidFromDn` has unit tests next to the parser. Existing LDIF suites updated where they asserted the old "invents a UID" behaviour. 1034 web tests, 142 server tests, typecheck clean.
**Merged** 2026-09-04 as coffey-labs/ihasmail@4d23cef5112c
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the half of #223 that was left undone when it was closed. Reported again by the submitter's colleague at LINET: duplicate checking landed for vCard and never for LDIF, so re-importing an address book still leaves a second copy of everything.
It was left open because I wanted the matching key decided rather than guessed. The decision had already been made on #174 — an attribute that can change is fine, because it will not have changed between two imports minutes apart — and I closed the issue without acting on it.
What it does
uidFromDnderives a namespaced, stable uid from an entry's distinguished name (urn:x-ihasmail:ldif:…), normalised for the case and spacing two exports of one directory differ in. The namespace is what keeps it from being read as a UID a vCard author meant.dngets an identity of its own and duplicates on re-import, as everything did before.Three things worth knowing
Matching is per address book. Two customer directories that each hold a
cn=John Smithstay two people as long as they are filed separately; imported into one book they merge. That is the one way this can be wrong, and separate books are the escape hatch.The look-alike count stays and now means something narrower — entries
dnmatching could not catch: one whosednmoved between exports, and anything imported before there was adnto match on. Still counted, never merged, because name-plus-email is a guess wrong in both directions.A file with two entries under one
dnis malformed, since a directory cannot hold that, and now becomes one card rather than two sharing an identity.Tests
New
ldif-dedupe.test.tscovers update-not-duplicate, the merge, that the card is not refiled into the import target, per-book scoping, case and spacing, and created/updated reported apart.uidFromDnhas unit tests next to the parser. Existing LDIF suites updated where they asserted the old "invents a UID" behaviour. 1034 web tests, 142 server tests, typecheck clean.Merged 2026-09-04 as coffey-labs/ihasmail@4d23cef511
Rebuilt from: git history, session transcript.