v4.1.0 — one slug per station, not one per catalogue row
Four of the slugs 4.0.0 published were second names for water that already had
one. This retires them and adds the check that would have caught them.
Every slug published before 4.0.0 is untouched. The four names retired here
were allocated in 4.0.0 and nothing consumed them: no page, no sitemap row, no
link.
The four
| apart | kept | retired |
|---|---|---|
| 1.5 m | boundary-pass |
turn-point |
| 39.8 m | vancouver |
vancouver-bc |
| 42.3 m | victoria |
victoria-harbour |
| 63.3 m | point-atkinson |
point-atkinson-west-vancouver-bc |
Each pair is one station entered twice — a curated identity plus the provider's
own catalogue row. chs-victoria already carried "victoria harbour" as an
alias, so the curated entry knew about its own duplicate. Both ids in each pair
now resolve to the kept slug.
The retired names redirect rather than 404. Each is recorded in
formerSlugs on the surviving record, so a consumer building a redirect map
from the registry serves them. None is reallocated, then or later.
Why a uniqueness check could not have found this
The allocation ladder manufactures uniqueness. A duplicate loses the base slug
to whichever id sorts first and falls to the region rung, so vancouver and
vancouver-bc were both allocated and both read as careful disambiguation.
The slugs are unique — uniqueness passing is not evidence of anything.
Position is the only signal that survives the ladder.
New
findNearbyPairs, haversineMetres, NEARBY_METRES. A position sweep for
duplicate identities within a kind. station-metadata slugs now reports pairs
within 100 m after writing, so the next allocation surfaces them while the
names are still free.
It reports and never fails. noaa/jx0302 and noaa/jx0303 are 4.4 m apart and
genuinely distinct lighted buoys, with different flood directions and different
M2 amplitudes — distance cannot tell a duplicate from a close pair, so a person
decides.
Indexed rather than scanned: bucketed into ~0.01° cells and compared against
adjacent cells, it sweeps all 4,690 stations in 11 ms, where all-pairs would
be ~7.3M distance calculations. A test asserts the bound.
checkSlugTable takes an optional fourth argument, the slugs recorded in
formerSlugs. A slug may move when its old value is recorded — the rule
registry.yaml has documented since before the table existed, now enforced. A
move with no record is still reported.
Unchanged
4,690 slugs, 3,823 tide and 867 current. The table still covers exactly the
catalogue it was generated against, and no slug published before 4.0.0 has ever
moved.