- Shipped
- August 29, 2026 at 2:27 AM UTC
- Author
- Kamo
- Commit
- 6e0422c
Three rules, and they only make sense together. **Naming a domain claims nothing.** An FQDN was globally unique here, so the first organization to enter "acme.com" locked out every other one — including the company that actually owns acme.com, which then had no way in at all. Someone who has proved nothing must not be able to deny a domain to someone who can. Duplicate rows across organizations are now legal; a repeat within ONE organization is still refused, because that is a mistake with no meaning rather than a competing claim. **Only a verified domain routes.** findByDomain now requires ownership_verified on the top-level row, so a host goes to the organization that proved control of its DNS rather than to whoever typed it first. Without this pairing the first rule would be an open door: claim somebank.com and wait. An alias inherits its parent's verification — the TXT challenge is only ever issued for the apex — and that is read off the explicit parent join, never as d.parent.ownershipVerified, which Hibernate emits as an inner join over the whole query and would drop every apex row. **Verifying takes the domain off everyone else.** setOwnershipVerified revokes the same FQDN wherever else it is held, so there is at most one holder at a time. This is not a courtesy: two verified rows are both routable, and the resolver would hand the traffic to whichever the database returned first. An organization that verified a domain last year and has since let it lapse would keep receiving the mail of whoever owns it now. The loser's DNS verification goes with it, because it describes a hostname that no longer points at them. Allowing duplicates then breaks every lookup that assumed one row, and one of those was not a crash but a hijack: upsertDomain matched on the domain NAME and then reassigned whatever it found to the calling organization, so an upsert would have quietly taken another organization's domain away from it. It is organization-scoped now. The rest — findByDomain, updateSslInfoByDomain, transferDomainOwnership — went through a single holderOf(), which prefers the verified row and otherwise takes the lowest id, so a domain's owner cannot change between two requests. Left as Optional bindings they would have thrown **************** on data the schema now permits.