Upload spool + retry, analog noise squelch, MQTT error, Discord pause (node-26#44 #46 #21 #34) #47

Merged
logan merged 6 commits from fix/44-upload-spool into main 2026-11-01 06:39:40 -05:00
Owner
  • #44: a failed upload no longer deletes the recording. It is spooled to disk (512 MiB / 72h caps) and retried with backoff, including 403 (not yet approved). Only 400/413 are dropped.
  • #46 + the pin half of #29: OP25 is pinned to gr310 5b58335; analog defaults to noise squelch, with mode/level plumbed to the dashboard.
  • #21: mqtt_manager.last_error; health tells refused from unreachable.
  • #34: retained discord_join redelivery no longer undoes a local pause.

Server companions server-26#301/#302 are on feat/ai-pipeline-review (not merged yet).

Tests: edge-node 347 pass, op25-container 18 pass, flake8 clean.

🤖 Generated with Claude Code

- #44: a failed upload no longer deletes the recording. It is spooled to disk (512 MiB / 72h caps) and retried with backoff, including 403 (not yet approved). Only 400/413 are dropped. - #46 + the pin half of #29: OP25 is pinned to gr310 5b58335; analog defaults to noise squelch, with mode/level plumbed to the dashboard. - #21: mqtt_manager.last_error; health tells refused from unreachable. - #34: retained discord_join redelivery no longer undoes a local pause. Server companions server-26#301/#302 are on feat/ai-pipeline-review (not merged yet). Tests: edge-node 347 pass, op25-container 18 pass, flake8 clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
logan added 6 commits 2026-11-01 06:39:27 -05:00
A local pause records the applied seq; only a strictly newer seq from C2
lifts it. The retained message the broker redelivers on reconnect carries
the same seq, so it now leaves the bots logged out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mqtt_manager.last_error = {kind, detail, epoch}: 'refused' from a non-zero
CONNACK, 'unreachable' from the initial connect loop or paho's
on_connect_fail, 'disconnected' otherwise. Cleared on connect. The /api/health
MQTT check now gives a key fix for a refusal and a network fix for an
unreachable broker instead of guessing from whether a key is on disk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
upload_recording deleted the file in `finally` even when the POST failed, so
any C2 outage, 5xx or timeout lost the call's audio for good.

- Delete only after a 2xx that carries a URL (C2 answers 200 {url: null}
  when its GCS write fails).
- Retryable failures (network, timeout, 5xx, 401/408/429, 2xx-no-url) move
  the file plus a JSON sidecar (call_id, talkgroup, system_id, audio epochs)
  into <recordings>/spool. A background task retries with exponential
  backoff and drains the spool on startup. Other 4xx are deleted with an
  ERROR naming the status.
- Bounded: UPLOAD_SPOOL_MAX_BYTES (512 MiB) and UPLOAD_SPOOL_MAX_AGE_HOURS
  (72), oldest evicted first, since a full host disk stops recording (#41).
- Crash-safe: the sidecar is fsynced and written before the audio moves in;
  a lost or corrupt sidecar is rebuilt from the filename instead of
  deleting the audio.
- The inline attempt uses UPLOAD_INLINE_TIMEOUT_SECONDS (30), so a
  black-holed C2 no longer stalls the segmenter for 120 s per call.
- call_end carries audio_upload=spooled|rejected|lost; a new API key wakes
  the spool; /api/status and the checkin report spool depth and the last
  upload outcome (toward #40).

Reviewed by drb-audio-review. Its two server-side findings (late uploads
correlated against now; no /upload idempotency) are filed separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- op25-container/Dockerfile: clone gr310 then check out 5b58335 (OP25_COMMIT
  build arg). That is the gr310 merge tk_drb_conv was verified against for
  #29 and the first carrying PR #296's NBFM noise/voice squelch. Changing
  the RUN line also busts CI's cached unpinned clone.
- Keys verified from op25_nbfm.py at that commit: nbfm_squelch_mode
  (power|noise|voice), nbfm_noise_squelch_db, nbfm_squelch_threshold, read
  from the channel dict.
- op25-container: ConfigGenerator.nbfm_squelch_mode (enum, default noise) and
  nbfm_noise_squelch_db, written onto the analog channel.
- edge-node: system config squelch_mode / squelch_db / squelch_threshold ->
  generate payload (validated, noise when absent); /api/listen and the
  dashboard's analog pane expose mode and level.

#29's CI test of tk_drb_conv against the real tk_p25.py is not done here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node waiting on approval would otherwise lose every call it records. The
512 MiB / 72h spool caps bound what a never-approved node keeps. 400 and 413
are still deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix stale log text for rejected uploads: 403 is now retried, so only 400/413 reach it (node-26#44)
CI / lint (push) Successful in 26s
CI / lint (pull_request) Successful in 30s
CI / test-op25 (pull_request) Successful in 37s
CI / test-op25 (push) Successful in 40s
CI / test (pull_request) Successful in 1m19s
CI / test (push) Successful in 1m19s
5983ae27be
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
logan merged commit c57a5b9068 into main 2026-11-01 06:39:40 -05:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/node-26#47