From 1b634835964f7db10083004f165358957af60771 Mon Sep 17 00:00:00 2001 From: rouggy Date: Mon, 24 Aug 2026 11:08:15 +0200 Subject: [PATCH] chore(lotw): log the record TQSL refused 'No QSOs processed' (exit 8) covers already-uploaded, out-of-certificate- date-range, and a station callsign that does not match the location being signed with -- TQSL's message does not say which. An operator whose QSO was refused and then accepted after a round trip through another logger is reporting a difference in the record itself, and the temp ADIF is deleted the moment TQSL returns, so that difference could not be seen. Logged on any non-zero exit: the record verbatim, the exit code and the station location. It is QSO data; the key password is not logged. --- changelog.json | 6 ++++-- internal/extsvc/lotw.go | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/changelog.json b/changelog.json index e3b3ef6..470793a 100644 --- a/changelog.json +++ b/changelog.json @@ -4,11 +4,13 @@ "date": "", "en": [ "Withdrawing deleted QSOs from Club Log is now paced and capped at 25 per deletion. Their delete endpoint is a real-time one, meant for an operator removing a contact they just mis-logged; Club Log watches the rate and blocks the IP of anything that batches through it. Past the cap OpsLog stops and says so — there is no bulk-delete API, and hundreds of removals belong on clublog.org, which has a tool for it.", - "Auto-call, withdrawn earlier, is now disarmed where it was remembered: a stored 'enabled' is switched off and written back the first time OpsLog reads it. The running guard already stopped this build from calling anyone, but the stored flag survived — and any build without that guard would key the transmitter for a feature with no switch left to turn it off." + "Auto-call, withdrawn earlier, is now disarmed where it was remembered: a stored 'enabled' is switched off and written back the first time OpsLog reads it. The running guard already stopped this build from calling anyone, but the stored flag survived — and any build without that guard would key the transmitter for a feature with no switch left to turn it off.", + "When TQSL refuses an upload, the log now carries the exact ADIF record it was given and the station location it was told to sign with. 'No QSOs processed' covers several unrelated causes and the temp file is deleted the moment TQSL returns, so the one piece of evidence that mattered was the one nobody could see." ], "fr": [ "Le retrait des QSO supprimés chez Club Log est désormais cadencé et limité à 25 par suppression. Leur point d'entrée de suppression est temps réel, prévu pour un opérateur qui retire un contact qu'il vient de mal enregistrer ; Club Log surveille le rythme et bloque l'IP de ce qui passe des lots par là. Au-delà de la limite, OpsLog s'arrête et le dit — il n'existe pas d'API de suppression en masse, et des centaines de retraits se font sur clublog.org, qui a l'outil pour ça.", - "L'appel automatique, retiré précédemment, est maintenant désarmé là où il était mémorisé : un « activé » enregistré est éteint et réécrit dès la première lecture par OpsLog. Le garde-fou à l'exécution empêchait déjà cette version d'appeler qui que ce soit, mais l'indicateur enregistré survivait — et toute version sans ce garde-fou passait à l'émission pour une fonction dont il ne reste aucun interrupteur." + "L'appel automatique, retiré précédemment, est maintenant désarmé là où il était mémorisé : un « activé » enregistré est éteint et réécrit dès la première lecture par OpsLog. Le garde-fou à l'exécution empêchait déjà cette version d'appeler qui que ce soit, mais l'indicateur enregistré survivait — et toute version sans ce garde-fou passait à l'émission pour une fonction dont il ne reste aucun interrupteur.", + "Quand TQSL refuse un envoi, le journal contient désormais l'enregistrement ADIF exact qui lui a été remis et l'emplacement de station demandé pour la signature. « No QSOs processed » recouvre plusieurs causes sans rapport et le fichier temporaire est supprimé dès que TQSL rend la main : la seule pièce à conviction utile était justement invisible." ] }, { diff --git a/internal/extsvc/lotw.go b/internal/extsvc/lotw.go index 7f0ac1e..6243f31 100644 --- a/internal/extsvc/lotw.go +++ b/internal/extsvc/lotw.go @@ -265,6 +265,22 @@ func UploadLoTW(ctx context.Context, cfg ServiceConfig, tempDir, adifRecord stri // callsign certificate's validity is silently left out, and reporting that as // success stamped it sent for ever. TQSL says which case it is in its output, // so the message is carried up rather than replaced with a guess. + if code != 0 && LogSink != nil { + // The ADIF that was handed to TQSL, verbatim, and how TQSL was called. + // + // TQSL says "no QSOs processed" for several unrelated reasons — already + // uploaded, outside the certificate's dates, or a STATION_CALLSIGN that + // does not match the station location it was told to sign with — and its + // message does not distinguish them. An operator whose contact was + // refused, then accepted after a round trip through another logger, is + // reporting a difference in THIS RECORD, and there is no way to see it + // afterwards: the temp file is deleted as soon as TQSL returns. + // + // Nothing secret here: it is a QSO, and the key password is not logged. + LogSink("lotw: tqsl exit %d for station location %q", code, loc) + LogSink("lotw: record was: %s", strings.TrimSpace(adifRecord)) + } + switch code { case 0: return UploadResult{OK: true, Message: "uploaded to LoTW"}, nil