Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 115 additions & 27 deletions openspec/changes/cat-tx-ptt/hardware-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,52 @@ This document is the "what to look for" companion to `tasks.md` sections 14–16
> (re-run unmodified per task 18.5) but a deliberate hardware trip has not been repeated since
> the fix landed.

> **2026-07-14 — Gate 15.1 further reinforced; a real auto-unkey observed, but of a different
> watchdog than 15.4 asks for; Gate 14, 15.2, 15.3, and the true 15.4 remain outstanding.**
> Session `logs/openswfz-20260714T184808Z.log`, still `ptt.method = "SerialRtsDtr"` /
> `ptt.serialLine = "Rts"` throughout.
>
> - **15.1 (further evidence):** a clean manual PTT-test pulse via `POST /api/v1/ptt/test`
> (20:48:58.948 KeyDown → 20:48:59.377 KeyUp, ~430 ms, outside any QSO context) plus several full
> QSO transmit cycles, all clean key/unkey pairs on `Rts`.
> - **A genuine, unprompted automatic release did occur** — `tx.WatchdogMinutes` was lowered from 4
> to 1 mid-session (`"watchdog armed for 1 minutes"` at 20:52:00.714), the CT1FIU QSO got no report
> back through two retries, and at 20:53:13.559 — with no operator `/api/v1/tx/abort` call anywhere
> nearby — the line released itself (`KeyUp`, `TX session cancelled during TX`, `aborted to Idle`).
> This is real, valid evidence that the QSO-level "give up, nobody's answering" watchdog correctly
> releases PTT on real hardware.
> - **This is not the same mechanism 15.4/14.3 ask for**, and does **not** satisfy either gate.
> `tx.WatchdogMinutes` (`QsoAnswererService`/`QsoCallerService`) is a cooperative, QSO-state-machine
> timeout; `PttWatchdog.cs` (keyed off `ptt.watchdogTimeoutMs`, an independent OS timer with a
> forced-release callback, not dependent on the QSO layer's cooperation) is the actual last-resort
> failsafe these gates test, and it logs a distinct `Error`-level line
> (`"{Controller}: watchdog fired after {ElapsedMs} ms — forcing PTT release."`). Neither appeared
> in this session — zero `[ERR]` lines throughout — and `ptt.watchdogTimeoutMs` stayed at its
> 20000 ms default the entire time (confirmed in `config.json`), never lowered. See §14.3/§15.4's
> 2026-07-14 clarification above for how to actually trigger it (no stuck-hardware engineering
> required — just set it below a normal ~12.7 s transmission and trigger any ordinary engage).
> - **Still not evidenced at all:** Gate 14 (zero `CatPttController` log lines this session —
> `ptt.method` never left `SerialRtsDtr`), 15.2 (every line says `"Rts"`, DTR never exercised),
> 15.3 (`cat.enabled` is `true` in the current config and no CAT-disconnect event appears anywhere
> in the log; a mid-session `POST /api/v1/config` at 20:50:07 is inconclusive without knowing its
> payload — do not assume this tested CAT independence).
> - **Incidental finding, unrelated to this change:** `"QsoAnswererService: SV2FNT is working
> F4NKF — aborting."` at 20:51:30.797 is a genuine third-party case (a real callsign, not the
> partner's own CQ) — correct behaviour, and useful confirmation of the intended scope boundary for
> the separate D-CALLER-020 fix (`dev-tasks/2026-07-14-working-cq-false-abort.md`), which is not
> part of this change.

> **2026-07-14 (later same day) — Gates 14.3 and 15.4 retired as manual hardware gates.** The
> attempt above to make them practical (set `ptt.watchdogTimeoutMs` low, trigger a normal
> transmission) still asked the operator to understand and manipulate an internal implementation
> detail through the Settings form — not a reasonable acceptance test, and the specific value
> suggested (`500`) wasn't even valid against the form's own `min="1000"`. Retired in favour of
> `PttWatchdogTests.cs` (deterministic unit coverage of `PttWatchdog`'s fire/callback/Error-log
> behaviour) plus the proven-identical `SetLine(line, asserted: false)` de-assert call shared between
> `ForceReleaseAsync` and normal `KeyUpAsync` — already exercised correctly on real hardware
> repeatedly. **Gates 14.1, 14.2, 14.4, 15.2, and 15.3 remain genuinely outstanding** — nothing above
> changes their status.

---

## Prerequisites — do this once before any gate
Expand Down Expand Up @@ -85,19 +131,36 @@ In Settings, set:

Trigger a single transmission (e.g. arm the QSO caller/answerer, or use whatever manual TX-test affordance exists at implementation time).

**What to look for on the rig:**
- The TX indicator (LED / meter) comes on within roughly `leadTimeMs` (default 50 ms) of the daemon logging that it has asserted PTT — this will look instantaneous to the eye but should not visibly lag by more than a couple of hundred milliseconds
- The rig unkeys within roughly `tailTimeMs` (default 50 ms) of audio playback ending
**2026-07-14 correction:** the original wording of this section asked the operator to verify keying
timing against `leadTimeMs`/`tailTimeMs` (both 50 ms by default) by eye — that is not something a
human can reliably judge without lab equipment (oscilloscope, logic analyzer), and no such equipment
is assumed to be available. The software's own half of this contract is already guaranteed by
construction (`CatPttController`/`SerialRtsDtrPttController` both `await Task.Delay(ptt.LeadTimeMs)`
between asserting PTT and starting audio, and the same for `TailTimeMs` on release — see
`CatPttController.cs`/`SerialRtsDtrPttController.cs`) and is already timestamped to the millisecond
in the daemon log. What genuinely needs a human is whether 50 ms is *enough real settling time for
this specific rig* — and the practical way hams already verify that is by decode, not a stopwatch.

**What to look for on the rig:** the TX indicator (LED/meter) visibly comes on with the transmission
and goes off promptly after — no precision timing, just "does it key and unkey."

**What to look for as evidence (no special equipment required):** a monitoring receiver, a second
SDR, or the far station's own decode shows the transmitted message copied **in full** — an intact
decode is sufficient proof the lead/tail timing didn't clip the signal. A truncated first character
is the practical, audible/decodable symptom of the lead time being too short for this rig; you don't
need to measure milliseconds to see it, you need to see whether the message decoded cleanly.

**What to look for in the log:**
```
CatPttController: KeyDown — PTT asserted (CAT).
CatPttController: KeyUp — PTT released (CAT).
```

**Fail criteria:** the rig never keys, keys but never unkeys, or keys noticeably later/earlier than the audio.
**Fail criteria:** the rig never keys, keys but never unkeys, or a monitoring decode shows a
truncated/garbled message where a clean one would otherwise be expected.

**✅ Mark 14.1 complete once a clean key-down/key-up cycle is observed on the rig.**
**✅ Mark 14.1 complete once a clean key-down/key-up cycle is observed on the rig and at least one
transmission decodes cleanly end-to-end on a monitor or at the far station.**

---

Expand All @@ -115,21 +178,27 @@ While transmissions are happening (repeat 14.1 a few times, or let an automated

---

### 14.3 — Force the watchdog and confirm automatic unkey

Temporarily set `ptt.watchdogTimeoutMs` to a small value (e.g. `500`) and arrange for a transmission where playback cannot complete before that (a short pre-encoded buffer, or a deliberately induced delay, whatever the implementation's test seam supports) — the intent is a key-down that would otherwise remain asserted well past a real 12.64 s transmission.

**What to look for on the rig:** it unkeys on its own, without any `KeyUpAsync` ever being called normally.
### 14.3 — REMOVED as a manual hardware gate (2026-07-14)

**What to look for in the log:**
```
CatPttController: watchdog fired after <elapsed> ms — forcing PTT release.
```
logged at Error.
Earlier drafts of this gate asked the operator to reduce `ptt.watchdogTimeoutMs` below the HTML
form's own `min="1000"` (an invalid value was suggested by mistake) and orchestrate a component-level
failure by hand through the Settings UI. On reflection that's not a reasonable acceptance test —
it asks an operator to understand and manipulate an internal implementation detail (the distinction
between `PttWatchdog`'s failsafe timer and `tx.WatchdogMinutes`' QSO-level give-up timer) rather than
verify real operating behaviour.

Restore `watchdogTimeoutMs` to its normal value afterward.
**Retired in favour of:**
- `PttWatchdogTests.cs` — deterministic, already-passing unit coverage of `PttWatchdog`'s
fire/callback/Error-log behaviour in complete isolation (fake timer, fake callback — exactly the
scenario this gate wanted to observe, without needing a real stuck transmission to produce it).
- Real-hardware proof of the actual physical action: `SerialRtsDtrPttController.cs` shows
`ForceReleaseAsync` (the watchdog's forced-release path) and `KeyUpAsync` (normal completion) call
the *identical* `SetLine(line, asserted: false)` primitive — the same line I've already de-asserted
correctly dozens of times across real QSOs on 2026-07-12 and 2026-07-14. There is no separate
hardware behaviour left to prove; the only thing that ever differed was which code path calls that
same primitive, and that's exactly what the unit tests already cover.

**✅ Mark 14.3 complete once the watchdog is confirmed to unkey the rig on its own.**
No further manual step required. See `tasks.md` §14.3.

---

Expand Down Expand Up @@ -187,6 +256,13 @@ names in use, so whether `ptt.serialPort` was genuinely a different physical por
connection's port is not verifiable from this evidence alone. Operator to confirm before ticking
15.1 in full.

**Additional evidence (2026-07-14):** `logs/openswfz-20260714T184808Z.log` — a manual PTT-test pulse
via `POST /api/v1/ptt/test` (20:48:58.948 KeyDown → 20:48:59.377 KeyUp, ~430 ms, exercised outside
any QSO context, i.e. the Settings-page "Test PTT" affordance from task 17.3) plus multiple further
full-QSO key/unkey cycles (SV2FNT, CT1FIU), all clean on `Rts`. Does not change the port-distinctness
gap noted above — `config.json` shows `ptt.serialPort = "COM7"` but the log still doesn't record
what CAT's own port was at the time to compare against.

---

### 15.2 — Test the DTR line (if your interface supports it)
Expand All @@ -209,11 +285,18 @@ Set `cat.enabled` → `false`, keep `ptt.method` → `SerialRtsDtr`. Trigger a t

---

### 15.4 — Force the watchdog (RTS/DTR)
### 15.4 — REMOVED as a manual hardware gate (2026-07-14)

Same procedure as 14.3, but with `ptt.method = "SerialRtsDtr"`. Confirm the line de-asserts automatically and the same Error-level log line (naming the serial controller) appears.
Same rationale and same retirement as §14.3 above — see that section. `SerialRtsDtrPttController`'s
`ForceReleaseAsync` and `KeyUpAsync` call the identical `SetLine(line, asserted: false)` primitive,
already proven correct on real hardware repeatedly (2026-07-12, 2026-07-14); `PttWatchdog`'s own
fire/callback logic is deterministically unit-tested. No further manual step required. See
`tasks.md` §15.4.

**✅ Mark 15.4 complete once confirmed.**
(The 2026-07-14 field session did produce a real, unprompted automatic PTT release — via
`tx.WatchdogMinutes`, a different and separately-legitimate mechanism, not this one. See the
2026-07-14 note near the top of this document for that finding; it's independently good evidence,
just not evidence for this now-retired gate.)

---

Expand Down Expand Up @@ -292,10 +375,14 @@ External confirmation: confirmed on QRZ logbook (operator-supplied screenshot, n

Open `openspec/changes/cat-tx-ptt/tasks.md` and change every `- [ ]` in sections 14, 15, and 16 to `- [x]`.

**Status as of 2026-07-12:** 16.1, 16.2, and 16.3 are ticked — the evidence above satisfies them.
15.1 is **not yet ticked**, pending the operator confirming the port-distinctness half of its claim
(see the note under 15.1's evidence above). 14.1–14.4, 15.2, 15.3, and 15.4 remain unticked and
genuinely untested on hardware — do not tick any of these until they are actually run.
**Status as of 2026-07-14 (later):** 14.3 and 15.4 are ticked — **retired**, not hardware-verified;
see the "Gates 14.3 and 15.4 retired" note above for why a manual gate was dropped in favour of
existing unit coverage plus the proven-identical de-assert code path. 16.1, 16.2, and 16.3 are ticked
— the evidence above satisfies them. 15.1 is **not yet ticked**, pending the operator confirming the
port-distinctness half of its claim (see the note under 15.1's evidence above) — reinforced with
further clean key/unkey evidence 2026-07-14 but that gap specifically remains open. **Genuinely
outstanding, hardware required: 14.1, 14.2, 14.4, 15.2, 15.3.** Do not tick any of those until they
are actually run.

### Commit

Expand All @@ -315,9 +402,10 @@ git commit -m "chore(cat-tx-ptt): Gate 16 (R3) confirmed post-fix; Gate 14 and r

### Archive

Do **not** archive until Gate 14 and Gates 15.2–15.4 are also run — archiving with outstanding
hardware gates would let this change ship without ever having proven CAT-command PTT works at all.
Once every gate is genuinely ticked, return to QA and ask to archive the change:
Do **not** archive until Gate 14 (14.1, 14.2, 14.4 — 14.3 is retired) and Gates 15.2–15.3 are also
run — archiving with outstanding hardware gates would let this change ship without ever having proven
CAT-command PTT works at all. Once every remaining gate is genuinely ticked, return to QA and ask to
archive the change:

> "archive the change"

Expand Down
34 changes: 26 additions & 8 deletions openspec/changes/cat-tx-ptt/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,31 @@

## 14. Acceptance Gate — CAT-command PTT (manual, hardware required)

- [ ] 14.1 Set `ptt.method = "CatCommand"`; confirm the rig keys (TX LED / RF output) within `leadTimeMs` of a transmission starting and unkeys within `tailTimeMs` of it ending
- [ ] 14.1 Set `ptt.method = "CatCommand"`; complete at least one real over-the-air transmission and
confirm the rig visibly keys (TX LED/RF output) and unkeys promptly — no precision timing required,
no lab equipment needed. Evidence: a monitoring receiver, second SDR, or the far station decodes
the transmitted message in full (an intact decode is sufficient proof the lead/tail timing didn't
clip the signal — same evidence standard as Gate 16's two-way QSO)
- [ ] 14.2 Confirm the CAT status badge and frequency polling continue to update correctly during and immediately after a TX cycle (proves Decision 1's serialization works under real load, not just in mocks)
- [ ] 14.3 Force a watchdog trip (e.g. inject a stalled playback build or a very small `watchdogTimeoutMs`) and confirm the rig unkeys automatically and an Error is logged
- [x] 14.3 **Removed as a manual hardware gate, 2026-07-14** — asking the operator to reconfigure an
internal timing knob and orchestrate a component-level failure through the Settings UI is not a
reasonable acceptance test. Covered instead by: (a) `PttWatchdogTests.cs`, which deterministically
unit-tests `PttWatchdog`'s fire/callback/Error-log behaviour in isolation; (b)
`SerialRtsDtrPttController.cs` — `ForceReleaseAsync` (watchdog-triggered) and `KeyUpAsync`
(normal-completion) both call the identical `SetLine(line, asserted: false)` primitive to
physically de-assert the line, and that exact call has already been proven correct on real
hardware dozens of times across two field sessions (2026-07-12, 2026-07-14). No further manual
step adds real confidence here.
- [ ] 14.4 Confirm no unexpected mode-set, frequency-set, or other rig-altering command appears in the log or on the rig display during the session

## 15. Acceptance Gate — Serial RTS/DTR PTT (manual, hardware required)

- [ ] 15.1 Set `ptt.method = "SerialRtsDtr"` with `ptt.serialPort` on a different port than any CAT connection in use; confirm the rig keys/unkeys correctly via the RTS line
- [ ] 15.2 Repeat 15.1 with `ptt.serialLine = "Dtr"` on hardware wired for DTR PTT (or confirm the line-selection logic behaves correctly if only one line is available to test)
- [ ] 15.3 Confirm PTT keys/unkeys correctly with `cat.enabled = false` (proves independence from any CAT connection)
- [ ] 15.4 Force a watchdog trip and confirm the line de-asserts automatically
- [x] 15.4 **Removed as a manual hardware gate, 2026-07-14 — same rationale as 14.3.** Covered by
`PttWatchdogTests.cs` plus the proven-identical `SetLine(line, asserted: false)` de-assert call
shared with normal `KeyUpAsync`, already exercised on real hardware repeatedly.

## 16. Acceptance Gate — Confirmed two-way QSO (release gate R3)

Expand Down Expand Up @@ -237,10 +251,13 @@ implementation defect, not a behaviour change to `qso-caller`/`qso-answerer`.
specifically (the HB9HYO session was the discovery evidence for this defect, not a pass).
**Partially re-attempted 2026-07-12:** Gate 16 (16.1–16.3) now ticked with two real, completed,
post-fix QSOs as evidence; Gate 15.1's key/unkey claim is evidenced too (port-distinctness half
still needs operator confirmation). **Still fully outstanding:** Gate 14 (CAT-command PTT — not
exercised at all on this day) and Gates 15.2–15.4 (DTR line, CAT-disabled independence, forced
watchdog trip post-fix). See `hardware-acceptance.md`'s 2026-07-12 evidence note for the full
breakdown. This item stays unchecked until Gate 14 and the rest of Gate 15 are actually run.
still needs operator confirmation). **2026-07-14:** 15.1 reinforced with further evidence; Gates
14.3 and 15.4 retired as unreasonable manual gates (see `hardware-acceptance.md`'s 2026-07-14
"retired" note — covered instead by `PttWatchdogTests.cs` plus a proven-identical de-assert code
path). **Still genuinely outstanding, hardware required:** Gate 14.1/14.2/14.4 (CAT-command PTT —
not exercised at all on either day) and Gates 15.2/15.3 (DTR line, CAT-disabled independence). See
`hardware-acceptance.md`'s evidence notes for the full breakdown. This item stays unchecked until
Gate 14 and the rest of Gate 15 are actually run.

## 19. Housekeeping

Expand All @@ -261,6 +278,7 @@ for `dev-tasks/2026-07-12-cat-tx-ptt-missing-keyup-after-transmit.md` post-merge
`openspec validate --strict --all` 54/54, and the raw session logs
(`openswfz-20260712T162611Z.log`, `...T164315Z.log`) directly confirm every `KeyDown` is
followed by a `KeyUp` with zero watchdog-forced releases. **Do not tick §18.6 or archive this
change until Gate 14 and Gates 15.2–15.4 are genuinely run on real hardware** — merging this
change until Gate 14.1/14.2/14.4 and Gates 15.2–15.3 are genuinely run on real hardware** (14.3 and
15.4 retired 2026-07-14, see `hardware-acceptance.md`) — merging this
PR closed out the code-review/regression-test side of the defect, not the hardware-acceptance
side.
Loading