fix(kiss): key the transmitter for every emission (#1259) - #1287
Merged
Conversation
openpulse-kiss declared openpulse-radio in its Cargo.toml and never used it: no PttController at all, [modem] ptt_backend unread, nothing logged. An operator running the APRS path with ptt_backend = "rigctld" played audio into an unkeyed transceiver — the TX counter incremented, no warning appeared, and only VOX worked. Same class as #1250 and #1251, but silent. Its own comment claimed "This TNC manages PTT and channel access itself", false for the PTT half; corrected. Two corrections from the design review, both changing the TESTS rather than the fix: - The issue body was wrong that KISS carries a StationIdTimer. It deliberately carries none — AX.25 puts the source callsign in every frame's address field. That removes the natural positive control ARDOP's equivalent test had, so each test keys the shared PTT directly first. - The source scanner could not be copied. Both existing ones match `engine.transmit`; KISS writes multi-line chains where `engine` and `.transmit(` are five lines apart, so a copied pattern would match nothing while its planted single-line control still passed — a vacuous gate with a green self-check. The pattern is `.transmit(` and the planted control is multi-line. The scanner then reproduced #1192 on its first run: it flagged a doc comment saying relay forwarding "must therefore call engine.transmit(...)" — prose read as a call site, in a scanner written after #1192 fixed that class elsewhere. It strips comment lines now, and says why. Guard placement follows ARDOP: the engine lock is bound first and passed in, so the guard drops before the mutex releases and the RX poll after a data emission never runs against a keyed rig. KISS's lock was a statement temporary, so a `let _guard` beside it would have outlived it. #1263 does not block this: its live bite is the daemon's manual PttAssert, and KISS has no host PTT command. Its two sites are sequential in one worker thread, so there is no nesting overlap. Results: 2/2 new, crate green, scanner FAILS against the restored unkeyed transmit (restored by sha256sum), clippy clean, REACH and TRACE pass. Implements: REQ-PTT-01 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
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.
Closes #1259. Builds on #1258.
The defect
openpulse-kissdeclaredopenpulse-radioin itsCargo.tomland never used it: noPttControllerat all,[modem] ptt_backendunread, nothing logged to say so. An operator runningthe APRS path with
ptt_backend = "rigctld"played audio into an unkeyed transceiver — the TXcounter incremented, no warning appeared, and only VOX worked.
Same class as #1250 (ARDOP) and #1251 (mesh), but silent: ARDOP at least warned on an unknown
backend. And the crate's own comment claimed "This TNC manages PTT and channel access itself",
which was false for the PTT half. Corrected — the CSMA half was always true, and KISS is the only
shipping front-end that enables carrier sense.
Two corrections from review, both changing the TESTS rather than the fix
1. This issue's body was wrong that KISS carries a
StationIdTimer. It deliberately carriesnone: AX.25 puts the source callsign in every frame's address field, satisfying §97.119 without a
separate ID cycle. That removes the natural positive control ARDOP's equivalent test had — its ID
path keyed before the fix, so a spy seeing zero asserts proved the spy was mis-wired rather than the
code correct. Each test here keys the shared PTT directly first, as
ptt_keys_every_daemon_transmitdoes.
2. The source scanner could not be copied. Both existing ones match
line.contains("engine.transmit"). KISS writes multi-line chains whereengineand.transmit(arefive lines apart, so the copied pattern would match nothing — a vacuous gate whose planted
single-line control still passes. The pattern here is
.transmit(, and the planted control isdeliberately multi-line.
And the scanner reproduced #1192 on its first run
It flagged line 274 of
bridge.rs— a doc comment saying relay forwarding "must therefore callengine.transmit(...)". Prose read as a call site, in a scanner written after #1192 fixed thatexact class elsewhere in this repo. It strips comment lines now, and the comment says why.
Guard placement
ARDOP takes the engine lock first, then keys, so the guard drops before the mutex releases and the
RX poll that follows a data emission never runs against a keyed rig. KISS's lock was a statement
temporary inside the
transmitexpression, so alet _guardbeside it would have outlived it. Bothsites now bind the lock in a scope and pass it to
keyed_transmit.A PTT assert failure skips the emission — transmitting anyway is the defect being closed.
#1263 does not block this
Its live bite is the daemon's manual
PttAssertcommand, and KISS has no host PTT command at all(its control frames are advisory). The two transmit sites are sequential in one worker thread, so
wiring it adds no nesting overlap.
observer = None: KISS has no host response channel for PTTedges, unlike ARDOP.
Results
Sabotage: restoring the pre-fix unkeyed transmit fails the scanner, naming the line. Restored by
sha256sum.Process note
I began this work in the same checkout while #1258's gate was running, which makes that verdict
unattributable — the drift guard hashes the whole tree and the rule has no exception for "a different
crate". Caught before quoting the verdict, but only after ~20 minutes; that gate was killed and re-run
clean. Recorded in the ledger because the trigger I was watching for was wrong: not "am I editing the
gated files" but "is a gate running at all".
🤖 Generated with Claude Code
https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6