fix(manta-spot,manta-server,manta-cli): MAN-136 — populate dxDxcc and make unresolved geography explicit - #140
Open
catalyst-cloud-connector[bot] wants to merge 1 commit into
Open
Conversation
catalyst-cloud-connector
Bot
force-pushed
the
MAN-136
branch
from
September 8, 2026 00:55
559d820 to
545f3da
Compare
|
Tick the box to add this pull request to the merge queue (same as
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
manta's
:7301JSON Lines stream emitted"dxDxcc": null/"deDxcc": nullon every spot, including ones whose callsign
cty.datresolves cleanly.dispensa's
spots.v1.schema.jsondeclaresdxDxccrequired andnon-nullable, so cqdx's own ingest would reject the batch on that field
alone — the exact failure this ticket names. Separately, when a callsign
genuinely can't be resolved (reachable in production via MAN-28's Watch List
allowlist, which bypasses
cty.is_allocated()), the geography fields fellback to a plain empty string /
0— indistinguishable on the wire from real,resolved data.
This implements broad-review decision D10
(
docs/DECISIONS/2026-09-06-broad-review-decisions.md): vendor a small ADIFDXCC entity-number table alongside
cty.datand resolve it inmanta_spot::cty, rather than waiting on a dispensa contract change.What changed
manta-spot— newcrates/manta-spot/data/dxcc.tsv(346 rows, ~6.9 KB),derived from AD1C's
cty.csv(scripts/gen-dxcc-table.sh, new, regenerable)and keyed on
cty.dat's primary prefix, not entity name — measured346/346 matches on prefix vs. 343/346 on name (three AD1C entity-name
renames in a six-week window; ADIF 3.1.7's own release notes independently
renamed five other entities).
cty::Entrygainsdxcc: Option<u16>,resolved once per entity during
Table::parsevia a newTable::parse_with_dxccthat the publicparsesignature delegates to(zero call-site churn across
manta-cli/manta-server/existing tests).Starred subentities correctly inherit their parent's DXCC number (e.g.
Sicily's
IT9prefix → Italy's 248).manta-server—SpotMessage::dx_dxcc/de_dxccchange fromOption<i64>toi64, makingnullunrepresentable at the type level.Three new exported sentinel constants (
spot_message.rs):UNKNOWN_DXCC = -1,UNKNOWN_CONTINENT = "",UNKNOWN_CQ_ZONE = 0.UNKNOWN_DXCCis deliberately not0— ADIF defines entity code 0 as"None: the contacted station is known to not be within a DXCC entity" (a
specific, false claim to make about a call manta simply couldn't resolve).
UNKNOWN_CONTINENT/UNKNOWN_CQ_ZONEkeep their pre-existing values(unchanged wire bytes for that half) but are now named, documented, tested
and counted instead of an unlabeled
unwrap_or_default().dxLat/dxLonstay
Option<f64>and staynullwhen unresolved — the one contract-legal"unknown geography" signal already available to consumers with no schema
change. The stale in-code comment claiming
dxDxcc/deDxccwere nullableon dispensa's contract (which contradicted three independent broad-review
readings of the actual schema) is removed and replaced with the accurate
contract description, cited against the new decision record.
manta-servermetrics —manta_spots_unresolved_geography_total(
AtomicU64, renders at 0 before any spot fires, per the "no silentloss anywhere in the pipeline" invariant in
ARCHITECTURE.md§8).manta-cli— counted once per spot at publish time (main.rs, next torecord_spot()), not insideSpotMessage::from_spot(which runs once perconnected client, not once per spot — counting there would scale with
client count). The increment condition checks whether the resolved cty
entry actually carries a DXCC number (
cty.lookup(call).and_then(|e| e.dxcc).is_none()), not merely whether the callsign resolved at all — astrict superset that also catches
cty.dat/dxcc.tsvdrift (geographyresolves but the entity has no DXCC row), so that state can't silently ship
dxDxcc: -1with the counter still at zero. The station's ownstation_callsignside is checked too, since it's operator config and notguaranteed cty-resolvable; that check is precomputed once at server startup
rather than re-looked-up per spot, since config can't change mid-process.
Docs — new
docs/DECISIONS/2026-09-07-man136-dxcc-and-unknown-geography-sentinels.mdrecords the contract, the prefix-vs-name measurement, each sentinel's
rationale (including why ADIF 0 isn't reused), what consumers should key on,
and the still-open cross-repo item (dispensa
Q-0028, open since2026-07-14) as a follow-up rather than a blocker.
ARCHITECTURE.md§6.2/§7/§8and both
wiki/pages/spot-output-contract.md/wiki/pages/spot-validation.mdupdated to match (the wiki previously saidthe dispensa schema was "not yet frozen" — no longer accurate; it also didn't
mention the MAN-28 allowlist as the mechanism that makes an unresolvable
call reachable in production).
Not touched: the telnet/RBN surface (
:7300) never constructs aSpotMessage, so it's unaffected.dxGrid/deGridstaynull—cty.datcarries no grid data. No dispensa schema was vendored or negotiated; per D10
this ships from manta's side without needing cqdx/dispensa to act first.
Why this shape
dxcc.tsvrather than switching to AD1C'scty.csvwholesale keeps the existing, battle-tested
cty.datparser (exact-callaliases, per-alias zone overrides, portable-suffix matching) untouched —
only the previously-discarded primary-prefix field gets a second, narrow
use.
during implementation (not by preference): a six-week version gap between
the vendored
cty.datand a freshly-fetchedcty.csvhad alreadyrenamed 3 of 346 entities but changed zero prefixes.
cty.csv(same author, samelicense posture as
cty.dat, and its column 3 is the ADIF DXCC entitycode) rather than a hand-curated scrape of ADIF's published enumeration —
keeps the vendored table reproducible from one documented script instead
of an unverifiable manual transcription.
implicit, indistinguishable-from-real fallback on a required field) with
one consistent sentinel pattern, and this also closes the geography-null
finding already on file in MAN-45 — that ticket's fix was never present in
this repository's history, so its design (constant names, counter shape,
call-site placement) was reproduced here verbatim rather than assumed
landed, avoiding a future merge conflict if MAN-45 lands separately.
Testing
cargo test -p manta-spot -p manta-server -p manta-cli: 315 passed, 0failed (net new/changed tests across all three crates; one previously
green test that pinned the old null-on-every-spot behavior,
dxcc_entity_numbers_are_null_not_fabricated, was deleted and replaced byan assertion of the correct resolved number rather than the absence of
one).
cargo test -p manta-engine -p manta-dsp -p manta-decode -p manta-input -p manta-testkit -p manta-soak-harness:163 passed, 0 failed (unaffected crates, run as a regression check).
every_entity_in_the_vendored_cty_dat_resolves_an_adif_dxcc_number, failsloudly if a future
cty.datrefresh adds an entitydxcc.tsvdoesn'tcover, instead of silently downgrading that entity's spots to the unknown
sentinel forever.
json_stream_acceptance.rs's socket-level test now asserts a non-nulldxDxccover a real TCP connection — the closest in-repo proxy for "acqdx-shaped consumer accepts this batch."
crates/manta-spot/data/dxcc.tsvwas verified byte-identical to a livere-run of the generator against upstream
cty.csv(fetched two days afterthe file was committed) — the committed table is genuinely reproducible,
not hand-edited.
cargo clippy --workspace --all-targets -- -D warningsandcargo fmt --all --check: both clean.line against dispensa's actual
spots.v1.schema.json(no dispensacheckout available here — the schema isn't vendored in this repo by
design) and a live daemon/socket manual check (no audio/SDR hardware in
this container). The full
cargo test --workspacein one invocationcannot complete in this container's constrained disk (
rust-lldruns outof space linking
manta-engine's test binaries); coverage was obtainedinstead via two
cargo clean-separated batches, both green, coveringevery crate — 478 tests total, 0 failed, 6 pre-existing (unrelated)
#[ignore]s.Known follow-ups (not blocking, carried forward against the new decision record)
UNKNOWN_CONTINENT/UNKNOWN_CQ_ZONE's values were never checked againstdispensa's actual per-field schema constraints (only
dxDxcc'snullability was confirmed via three independent broad-review readings) —
unverifiable in this repo since the schema isn't vendored here; this
branch does not change those two fields' wire values, only names/tests/
counts them.
parse_dxcc_table's dedup silently keeps the first row on a duplicatejoin key; there are zero duplicates in the table today, but nothing
currently guards against a future star-stripped prefix collision.
station_callsign(operator misconfiguration) currentlydrives the same counter as a genuine unresolvable dx callsign
(data-quality signal); a startup warning would let an operator tell those
apart at a glance.