Skip to content

fix(kiss): key the transmitter for every emission (#1259) - #1287

Merged
dc0sk merged 1 commit into
mainfrom
fix/1259-kiss-ptt
Sep 6, 2026
Merged

fix(kiss): key the transmitter for every emission (#1259)#1287
dc0sk merged 1 commit into
mainfrom
fix/1259-kiss-ptt

Conversation

@dc0sk

@dc0sk dc0sk commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Closes #1259. Builds on #1258.

The defect

openpulse-kiss declared openpulse-radio in its Cargo.toml and never used it: no
PttController at all, [modem] ptt_backend unread, nothing logged to say so. 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 (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 carries
none: 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_transmit
does.

2. The source scanner could not be copied. Both existing ones match
line.contains("engine.transmit"). KISS writes multi-line chains where engine and .transmit( are
five 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 is
deliberately 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 call
engine.transmit(...)"
. Prose read as a call site, in a scanner written after #1192 fixed that
exact 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 transmit expression, so a let _guard beside it would have outlived it. Both
sites 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 PttAssert command, 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 PTT
edges, unlike ARDOP.

Results

GATE: PASS 0a1f7ab831097a41e244e8f0bfea1015faa8d7c3 clean 20260906T172529Z
        suites=325 tests_passed=2464 tests_failed=0

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

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
@dc0sk
dc0sk merged commit d838d3e into main Sep 6, 2026
8 of 9 checks passed
@dc0sk
dc0sk deleted the fix/1259-kiss-ptt branch September 6, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KISS TNC builds no PTT controller, ignores ptt_backend silently, and its comment claims it manages PTT itself

1 participant