Closes F-NSID-22.
validate_nsid refused any NSID with a dot-separated segment beginning
with a digit, which refuses names that exist on the network:
org.4chan.lex.getThing, cn.8.lex.stuff, test.12345.record, and
anything under an onion address, which is digit-leading by construction.
An NSID is a domain authority in reversed notation followed by a name, and the two halves have different rules. DNS labels may begin with a digit (RFC 1123 section 2.1), so authority segments may too. Only the first segment is further restricted — it is the TLD — and only the last, which is an identifier rather than a host label and so takes neither a leading digit nor a hyphen.
The cause was expressing all of that as one regex that required every
segment to start with a letter. Three rules flattened into one, and the
flattening was invisible because the crate's own tests agreed with it:
a.b.c and app.bsky.feed.post pass under either reading.
The validator now follows the reference's structure (validateNsid,
packages/syntax/src/nsid.ts:89-122) with each rule stated where it
applies. That also improves the refusals — a non-ASCII character is now
reported as a character-set violation rather than as whichever structural
rule it happened to trip first.
Two tests guard the shape of the fix rather than just its effect: one that the exemption reaches neither the first segment nor the name, and one that a hyphen is still refused at either end of any segment. "Digit-leading segments are fine" would be an easy and wrong way to make the six vectors pass.
Closes 7 of the 31 cases pinned in interop_syntax.rs — six vectors, one
of which the corpus lists twice. The harness's rot guard is what
surfaced them: unpinning was forced by the test, not remembered.