Skip to content

docs: name the model in every serial example now that the factory refuses to guess - #3416

Merged
morozsm merged 5 commits into
mainfrom
codex/docs-serial-model
Sep 15, 2026
Merged

morozsm merged 5 commits into
mainfrom
codex/docs-serial-model

Conversation

@morozsm

@morozsm morozsm commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

2 code (cli hint + its test) + 8 docs = 10 files, at the hard file ceiling and not over it (the coordinating session records this under the owner's 2026-09-03 delegation: every documented serial invocation and the CLI's own printed examples are one change)

Why

Owner ruling R59 (PR #3411) removes the serial backend's implicit IC-7610
default: create_radio on a SerialBackendConfig with no model refuses
instead of guessing. Every serial invocation documented without --model
therefore stops working the moment #3411 lands. This PR names the radio in all
of them, ahead of that merge.

Three already-broken LAN cases are fixed in the same pass, because they are the
same defect — a documented invocation that identifies no radio: README.md's
shell quickstart, README.md's Python quickstart, and the CLI parser's own
printed examples (_build_parser's epilog and _print_common_cli_hint).
Those never worked on the LAN path either. Measured on this branch's base
(695e56d87), create_radio(LanBackendConfig(host=…, username=…, password=…))
with no model=:

ValueError: Cannot resolve a radio profile: no profile, model, or matching
radio_addr identifies the radio. ...

_auto_discover_lan (cli/__init__.py) returns only the host IP — it never
supplies a model — and _run catches that ValueError and returns 1.

Grep, before and after

grep -rn "rigplane .*\(--backend serial\|--serial-port\)" docs/ README.md \
  | grep -v -- "--model" | grep -v yaesu-cat

Both numbers re-derived at head 3b724c15a (the "before" via git grep at the
merge base):

  • before (695e56d87): 37 lines
  • after: 4 lines, all in docs/guide/ic705-usb-setup.md (147, 151, 239, 260)

Those four are false positives of a line-based grep, not defects: each is the
first line of a backslash-continued invocation whose --model IC-705 sits on
the next line (read at head). Left untouched deliberately — see "Not fixed".

Files

File Change
docs/guide/cli.md 5 serial examples gain --model IC-7610
docs/guide/configuration.md 2 serial quick-start examples
docs/guide/ic7300-usb-setup.md 1 audio bridge example gains --model IC-7300
docs/guide/ic7610-usb-setup.md 19 serial examples
docs/guide/troubleshooting.md 3 serial examples
docs/radio-protocol.md 3 serial examples
README.md shell quickstart gains --model IC-7610; the Python quickstart's LanBackendConfig(...) gains model="IC-7610"
src/rigplane/cli/__init__.py _build_parser's epilog and _print_common_cli_hint: the printed web/serve examples gain --model IC-7610, and the hint's sentence narrows from "web can auto-discover the radio" to discovery finding the IP with the model still named. Both are help/hint strings; no behaviour
tests/test_cli.py TestPasswordResolution::test_discover_web_common_mistake_gets_human_hint asserts the new hint string
src/rigplane/profiles/LAYER.md the bullet said the resolve_radio_profile docstring documents a precedence order ranking profile against model. It does not: it says a RadioProfile or a non-blank profile/model name is a caller override that wins over radio_addr, and that ValueError is raised when none of the three identifies the radio. The bullet now says that

Verification

  • 34 rewritten markdown invocations at head (an added line whose stripped
    form starts with rigplane , joined across backslash continuations);
    33 parse under _build_parser().parse_args(...). The one failure is
    docs/guide/ic7300-usb-setup.md:174, for a pre-existing reason this PR does
    not introduce: the global --serial-port sits after the subcommand, and
    audio bridge has no --loopback. Each cause was confirmed on its own —
    rigplane audio bridge --serial-port X and rigplane audio bridge --loopback Y
    each give error: unrecognized arguments — and git show origin/main:docs/guide/ic7300-usb-setup.md shows the base line already had
    both.
  • Each of the 3 distinct invocations _print_common_cli_hint prints was
    scraped from the hint's own stderr (not transcribed) and driven through
    cli._run, with _auto_discover_lan stubbed to return a host and
    create_radio wrapped to stop the run the moment it returns. All three get
    past create_radio. Control: the same three with --model removed each
    return 1 with the resolve ValueError.
  • The README Python quickstart's create_radio(LanBackendConfig(...)) call was
    extracted from the file and executed — construction only, nothing connects:
    it returns an IcomRadio. The same extraction against the pre-fix block
    raised the resolve ValueError.
  • uv run pytest tests/test_cli.py tests/test_cli_model.py tests/test_cli_coverage.py -q --tb=short --timeout=300 --timeout-method=thread
    245 passed, 0 failed.
  • uv run ruff check src/ tests/ — All checks passed.
    uv run ruff format --check src/ tests/ — 742 files already formatted.
  • Mutations. (a) The hint invocation strings reverted to their modelless form →
    tests/test_cli.py:470 fails (1 failed, 244 passed). (b) model="IC-7610"
    removed from the README block → the extraction check raises the resolve
    ValueError. Both reversed; git diff before the final gate run showed only
    the intended change.
  • The doc-citation gate did not run on this PR. Its workflow
    (.github/workflows/doc-citation-gate.yml) triggers only on changes to
    .github/scripts/check-doc-citations.sh or to itself, and this PR touches
    neither. The script would not run locally either — it uses declare -A and
    this machine has bash 3.2. Verified by hand instead:
    git diff 695e56d87...HEAD -- '*.md' adds no relative markdown link and no
    file:line citation, so neither gate's input changed.

Not fixed — reported instead

The earlier sweep of the class "documented rigplane invocation that
identifies no radio" was markdown-only. Re-run over Python sources, the
class has these members in src/rigplane/cli/__init__.py:

  • _print_common_cli_hintfixed in this PR, see above.
  • The module docstring usage synopsis (the Usage: block at the top of
    the file): 17 lines, every one
    modelless (rigplane status [--host HOST] …). Not fixed — it belongs with
    the LAN-docs follow-up (T213). Two of its forms were measured as real
    subprocesses at head: rigplane --host <ip> status and
    rigplane --host <ip> audio rx --out … --seconds 1 each exit 1 with the
    resolve error.
  • _warn_web_host_ambiguity quotes three forms in its warning text.
    As subprocesses, the two it recommends (rigplane web --radio-host <ip>,
    rigplane --host <ip> web) exit 1 with the resolve error; the third is the
    form it warns against, and on this machine it fails earlier, at LAN
    discovery. Same follow-up.
  • The four Example: rigplane audio … strings in the audio subparser
    descriptions (rx, tx, loopback, bridge) are modelless. Same
    follow-up.

Not a member: the epilog's rigplane discover line needs no model — discover
is dispatched in main() and never reaches _run, so create_radio is never
called.

Markdown members remaining:

  1. LAN-path invocations with no --model/--radio-addr.
    git grep -nE '^\s*rigplane ' -- '*.md', minus lines matching --model or
    --radio-addr, minus the commands that never reach create_radio
    (discover, proxy, diagnose, validate, radio-validate, convert,
    --list-audio-devices, --version, --help, audio caps,
    audio probe-profile), leaves 147 lines across 19 files, 104 of them in
    docs/guide/cli.md. That is a line count — a backslash-continued
    invocation counts once, on its first line — and I did not execute them.
    Ticket T213.
  2. docs/PROJECT.md — the 2026-03-23 architecture note describes
    IC-7610 → Icom7610SerialRadio (default); the parenthetical goes stale with
    fix: refuse to build a serial radio without an explicit model #3411.
  3. docs/guide/ic705-usb-setup.md CLI section — broken for an unrelated
    reason: its examples use --device, --baudrate, --rx-audio-device,
    --tx-audio-device and the subcommands repl and rigctld, none of which
    the parser defines. Different class, left untouched — which is also why the
    four residual grep lines above stay.
  4. docs/guide/ic7300-usb-setup.md:174 — see Verification. The model fix
    is correct and independent of the two parse defects, and is kept.

Different class, reported only: src/rigplane/profiles/LAYER.md says
"adding a new radio is a TOML file plus zero Python changes" (Charter) and
"No Python changes required" (Common operations, "Add a new radio"). That is
false for
serial. backends/factory.py dispatches on five model names and raises
otherwise; rigs/x6100.toml and rigs/tx500.toml exist with no branch.
Measured:

create_radio(SerialBackendConfig(device=…, model='X6100'))
  -> ValueError: Unsupported serial model 'X6100'; supported: IC-705, IC-7300,
     IC-7610, IC-9700, X6200, and Yaesu FT-series (FTX-1, FT-710, ...).
create_radio(SerialBackendConfig(device=…, model='TX-500'))  -> same ValueError
create_radio(SerialBackendConfig(device=…, model='IC-7300')) -> Ic7300SerialRadio

Guardrails

Census at 3b724c15a0dcf525bf01874a1cd5c224a0db5321
(git diff --numstat origin/main...HEAD): 10 files, 64+/57- = 121 changed
lines
.

Over the 6-file soft threshold and at the 10-file hard ceiling; under both line
limits. One unit of work: a single mechanical substitution driven by one owner
ruling, applied to every place the removed default was relied on, plus the two
blocks of the same class that review found in files this PR had already
touched.

internal-identifier self-check — empty.

Linear: MOR-2425

🤖 Generated with Claude Code

Correction after review (c407b902)

The first review found the sweep incomplete: docs/guide/ic7610-usb-setup.md and docs/radio-protocol.md still built a SerialBackendConfig with no model, and both would have broken the moment #3411 lands. c407b902 names IC-7610 in each — the radio both documents are about — and in four adjacent allow_low_baud_scope / migration fragments, which live in docs/guide/ic7610-usb-setup.md and docs/guide/troubleshooting.md.

23ac194e then fixed a regression c407b902 introduced: it had written SerialBackendConfig(model="IC-7610", ..., …), and in Python a bare ... after a keyword argument is a positional argument, so three snippets that compiled before stopped compiling. All abbreviated calls now put the placeholder first — SerialBackendConfig(..., model="IC-7610", …) — including one LanBackendConfig call in ic7610-usb-setup.md that had the same defect before this PR. Verified at the head by compiling every one-line backend-config snippet in docs/** and README.md: 24 checked, 0 broken. Docs-only edits; the file count stays at 10.

Not fixed here, and not because they work. Of the thirty LanBackendConfig(...) constructions in docs/** and README.md, twenty-three pass neither a model nor an address and two pass radio_addr=0x42; all twenty-five refuse today, before #3411 changes anything — measured at 0a5bd7f6:

create_radio(LanBackendConfig(host="192.0.2.1", username="u", password="p"))
→ ValueError: Cannot resolve a radio profile: no profile, model, or matching
  radio_addr identifies the radio. … rigplane no longer guesses a default rig.

One example resolves by address (radio_addr=0xA4 → IC-705) and two pass a variable address. That path is not a substitute for naming the radio: those addresses are factory defaults and the operator can change them in the radio's own menu, which is exactly what the two 0x42 examples document.

They are left out because this PR is already at the 10-file hard ceiling, and they are recorded as follow-up work on MOR-2425.

…uses to guess

Owner ruling R59 (#3411) removes the serial backend's implicit IC-7610
default, so a documented serial invocation with no --model will exit 1
once that lands. Every serial example in docs/ now names its radio.

The LAN examples in README.md and the CLI parser epilog were already
broken before R59: LAN discovery returns only a host, and IcomRadio's
resolve_radio_profile() raises without a model or radio_addr. Verified:
create_radio(LanBackendConfig(host=...)) with no model raises ValueError
on this branch's base (695e56d). Those two now pass --model.

profiles/LAYER.md described the resolution precedence as ending in an
"IC-7610 default"; resolve_radio_profile's docstring says it raises.

No behaviour change — the epilog is a help string. Every rewritten
invocation was parse-checked against _build_parser().

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@morozsm
morozsm marked this pull request as ready for review September 9, 2026 14:19
morozsm and others added 2 commits September 9, 2026 10:41
…stake hint

Two blocks of the previous commit's class survived in files it touched.

README's library quickstart built LanBackendConfig(host=, username=,
password=) with no model. Executed verbatim on this branch, the
construction raised ValueError ("Cannot resolve a radio profile"), six
lines below the shell line the previous commit fixed. It now passes
model="IC-7610"; the construction was executed again from the fixed
README and returns an IcomRadio.

_print_common_cli_hint is the parser epilog's twin: three distinct
invocations printed to a user who has just made a mistake. Each was run
through cli.main's _run with LAN discovery stubbed to return a host and
create_radio wrapped to stop the run on success: all three now get past
create_radio, and all three fail with the resolve error once --model is
removed. Its sentence claimed 'web' can auto-discover the radio; the
narrower claim is that discovery finds the IP and the model must be
named.

profiles/LAYER.md said the resolve_radio_profile docstring documents a
precedence order ranking profile against model. It does not: it says a
RadioProfile or a non-blank profile/model name is a caller override that
wins over radio_addr. LAYER.md now says that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@morozsm

morozsm commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: BLOCKED 47176af

Head confirmed equal to the PR head:

$ git rev-parse HEAD
47176afd3988aaef2950f09956cb8575315e0ccf
$ gh pr view 3416 --json headRefOid -q .headRefOid
47176afd3988aaef2950f09956cb8575315e0ccf

BLOCKING: the sweep missed two copyable documented serial examples (the PR's central claim)

The PR claims "every documented serial invocation now names the model" / "names the radio in all of them". Two complete, copyable Python serial invocations still name no model, and both break when the sibling #3411 lands — including one in a file this PR edits.

docs/guide/ic7610-usb-setup.md:209 (in this PR's 19-example file):

config = SerialBackendConfig(
    device="/dev/cu.usbserial-111120",
    baudrate=115200,
    radio_addr=0x98,
    rx_device="IC-7610 USB Audio",
    tx_device="IC-7610 USB Audio",
)
radio = create_radio(config)

docs/radio-protocol.md:314 (in a file this PR edits):

serial_config = SerialBackendConfig(
    device="/dev/cu.usbserial-111120",
    baudrate=115200,
)
radio = create_radio(serial_config)

At this head both still silently build the removed default — exactly what R59/#3411 deletes:

$ uv run python - <<'PY'
from rigplane.backends.factory import create_radio
from rigplane.backends.config import SerialBackendConfig
r = create_radio(SerialBackendConfig(device="/dev/cu.usbserial-111120", baudrate=115200, radio_addr=0x98, rx_device="IC-7610 USB Audio", tx_device="IC-7610 USB Audio"))
print("ic7610:209 ->", type(r).__name__, getattr(r,"model",None))
r = create_radio(SerialBackendConfig(device="/dev/cu.usbserial-111120", baudrate=115200))
print("radio-protocol:314 ->", type(r).__name__, getattr(r,"model",None))
PY
ic7610:209 -> Icom7610SerialRadio IC-7610
radio-protocol:314 -> Icom7610SerialRadio IC-7610

And #3411's factory hunk raises for any absent/blank config.model, deliberately not resolving from radio_addr:

$ gh pr diff 3411 | sed -n '7,17p'
     if isinstance(config, SerialBackendConfig):
         # Route to model-specific serial backend
-        model = (config.model or "IC-7610").upper()
+        if config.model is None or not config.model.strip():
+            # R59/MOR-2425: the default this replaced drove an IC-7300 as an
+            # IC-7610. Not resolved from ``radio_addr`` either.
+            raise ValueError(
+                "Serial backend needs an explicit radio model: pass --model "
+                "(e.g. --model IC-7300); rigplane does not guess a default rig."
+            )
+        model = config.model.upper()

So the radio_addr=0x98 in the first block does not save it. Both need model="IC-7610"; no new files, so the 10-file ceiling is unaffected.

The same block at docs/radio-protocol.md:306 is the identical class already broken today and should be fixed in the same pass (the PR fixed README's LanBackendConfig twin but not this one):

$ uv run python -c "from rigplane import create_radio, LanBackendConfig; create_radio(LanBackendConfig(host='192.168.55.40', username='u', password='p'))"
ValueError: Cannot resolve a radio profile: no profile, model, or matching radio_addr identifies the radio. ...

Abbreviated siblings (docs/guide/ic7610-usb-setup.md:345,456, docs/guide/troubleshooting.md:85,540) use ... and are illustrative, not copyable — noted, not blocking.

What checked out

1. Merge brought only this PR's change. origin/main (f50aaf5) is an ancestor of HEAD; two-dot --numstat is exactly the body's 10 files, and no origin/main commit since the branch base 695e56d87 touched any of those 10 paths (git log --oneline 695e56d87..origin/main -- <the 10 files> → empty). No silent revert.

$ git diff origin/main HEAD --numstat
3	2	README.md
5	5	docs/guide/cli.md
2	2	docs/guide/configuration.md
1	1	docs/guide/ic7300-usb-setup.md
19	19	docs/guide/ic7610-usb-setup.md
3	3	docs/guide/troubleshooting.md
3	3	docs/radio-protocol.md
15	14	src/rigplane/cli/__init__.py
4	2	src/rigplane/profiles/LAYER.md
1	1	tests/test_cli.py

2. CLI serial class complete. After joining backslash continuations, a docs/README sweep for --backend serial/--serial-port/ICOM_SERIAL_DEVICE without --model leaves only yaesu-cat lines (no model concept). The four residual grep lines are the backslash false positives the body names:

$ grep -rn "rigplane .*\(--backend serial\|--serial-port\)" docs/ README.md | grep -v -- "--model" | grep -v yaesu-cat
docs/guide/ic705-usb-setup.md:147 ... :148:  --model IC-705 ...
docs/guide/ic705-usb-setup.md:151 ... :152:  --model IC-705 ...
docs/guide/ic705-usb-setup.md:239 ... :240:  --model IC-705 ...
docs/guide/ic705-usb-setup.md:260 ... :261:  --model IC-705 ...

No model env var, config-file model=, or systemd unit contradicts --model (rg ICOM_MODEL|RIGPLANE_MODEL → nothing; rig-profile model = fields are profile identity, not CLI).

3. Edited commands work. Printed epilog and both hint branches now carry --model IC-7610 and are printed verbatim; the LAN resolve path is real (create_radio(LanBackendConfig(...)) no-model → ValueError, with model="IC-7610"IcomRadio). Serial against a dead port fails fast and identically with and without the flag at this head, because #3411 is not in this branch:

$ uv run rigplane --model IC-7610 --backend serial --serial-port /dev/does-not-exist status
Error: Failed to connect serial session on /dev/does-not-exist: [Errno 2] could not open port ... (exit 1)
$ uv run rigplane --backend serial --serial-port /dev/does-not-exist status
Error: Failed to connect serial session on /dev/does-not-exist: ... (exit 1)

The ic7300 example's parse failure is pre-existing and correctly reported — git show origin/main:docs/guide/ic7300-usb-setup.md line 174 already had --serial-port/--loopback; at head it still gives error: unrecognized arguments: --serial-port ic-7300-usb-in --loopback BlackHole 2ch.

4. The three LAN cases were already broken on origin/main. backends/factory.py, backends/icom7610/, profiles/__init__.py, backends/config.py are byte-identical between origin/main and HEAD (git diff origin/main HEAD -- <those> empty), so the HEAD behaviour is the main behaviour: bare rigplane web finds 192.168.55.142 then Error: Cannot resolve a radio profile ... exit 1; the README Python block no-model raises.

5. profiles/LAYER.md bullet is correct. New text ("explicit profile or model name is a caller override that wins over radio_addr; ValueError when none of the three identifies the radio") matches profiles/__init__.py: resolve_radio_profile docstring and body order (profile → model → radio_addr → raise).

6. Code/tests/lint. uv run pytest tests/test_cli.py -q152 passed; the body's suite → 245 passed. ruff check and ruff format --check on both files pass. Mutation pin confirmed: changing the hint's rigplane --model IC-7610 web --radio-host ... to a modelless form makes tests/test_cli.py:470 fail (assert 'rigplane --model IC-7610 web --radio-host 192.168.55.40' in err), restored and re-passing.

7. Guardrails. First body line states the count; head is exactly 10 files (git diff origin/main...HEAD --name-only | wc -l → 10) and 56+/52- = 108 lines — at the file ceiling, under the line ceiling.

8. CI as found. gh pr checks 3416 --required: quick pass (1m35s), Agent Review Gate fail (no exact-head PASS yet — this comment supplies it for the code, but the blocking findings above are the verdict). quick ran on this exact head SHA.

REQUIRED BEFORE MERGE (prose/body only)

  • The body's doc-citation verification cites git diff 695e56d87...HEAD -- '*.md'. After the merge, 695e56d87 is an ancestor of origin/main, so that range spans all 79 of main's commits and no longer isolates this PR. The substance still holds under the correct range: git diff origin/main...HEAD -- '*.md' adds zero relative Markdown links and zero file:line citations (verified). Correct the command before merge.
  • Guardrail first line says 2 code + 8 docs = 10 files; the template wording is N code + M regenerated baselines = K files. Count is right and verified; align wording if convenient.

The ic7610-usb-setup.md Python factory example and the
radio-protocol.md LanBackendConfig/SerialBackendConfig factory example
constructed backend configs without model=; create_radio will refuse a
SerialBackendConfig with no model once the sibling validation lands, so
every documented construction now names IC-7610 explicitly. The two
low-baud scope fragments in ic7610-usb-setup.md and troubleshooting.md
name the model as well.

Part of MOR-2425

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@morozsm

morozsm commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: BLOCKED c407b90

Head confirmed:

$ git rev-parse HEAD
c407b902899ac13af7383914f1bd3299d7882fe6
$ gh pr view 3416 --json headRefOid -q .headRefOid
c407b902899ac13af7383914f1bd3299d7882fe6

BLOCKING: c407b902 turned three valid Python snippets into syntactically invalid ones

The two previously-blocked sites are fixed correctly, but the "adjacent fragment" edits pushed the ... placeholder after the new model= keyword, which is a SyntaxError. The three affected fragments compiled before this commit and do not now:

$ uv run python - <<'PY'
import ast
cases = {
 "ORIG ic7610 allow_low_baud": 'SerialBackendConfig(..., allow_low_baud_scope=True)',
 "NEW  ic7610 allow_low_baud": 'SerialBackendConfig(model="IC-7610", ..., allow_low_baud_scope=True)',
 "ORIG troubleshooting":       'SerialBackendConfig(..., allow_low_baud_scope=True)',
 "NEW  troubleshooting":       'SerialBackendConfig(model="IC-7610", ..., allow_low_baud_scope=True)',
 "ORIG ic7610 migration serial": 'SerialBackendConfig(device="/dev/cu.usbserial-111120", ...)',
 "NEW  ic7610 migration serial": 'SerialBackendConfig(model="IC-7610", device="/dev/cu.usbserial-111120", ...)',
}
for k,v in cases.items():
    try: ast.parse(v); print(f"{k:32} VALID")
    except SyntaxError as e: print(f"{k:32} SyntaxError ({e.msg})")
PY
ORIG ic7610 allow_low_baud       VALID
NEW  ic7610 allow_low_baud       SyntaxError (positional argument follows keyword argument)
ORIG troubleshooting             VALID
NEW  troubleshooting             SyntaxError (positional argument follows keyword argument)
ORIG ic7610 migration serial     SyntaxError (positional argument follows keyword argument)
NEW  ic7610 migration serial     SyntaxError (positional argument follows keyword argument)

Sites (all in this delta):

  • docs/guide/ic7610-usb-setup.md:346 — was SerialBackendConfig(..., allow_low_baud_scope=True) (valid), now SerialBackendConfig(model="IC-7610", ..., allow_low_baud_scope=True) (invalid).
  • docs/guide/troubleshooting.md:85 — same transformation, invalid.
  • docs/guide/troubleshooting.md:540 — same transformation, invalid (and the line also gained a stray 4th leading space relative to the 3-space list context).
  • docs/guide/ic7610-usb-setup.md:457SerialBackendConfig(model="IC-7610", device="…", ...): invalid before and after (its preceding LanBackendConfig(host="…", ...) was already invalid too), so not a regression, but it should be corrected in the same pass.

Fix is one commit: put ... first, e.g. SerialBackendConfig(..., model="IC-7610", allow_low_baud_scope=True).

Delta review

1. The two blocking sites name the doc's own radio and still match their surroundings. docs/guide/ic7610-usb-setup.md:209 (model="IC-7610", device="/dev/cu.usbserial-111120", radio_addr=0x98, rx/tx_device="IC-7610 USB Audio") and docs/radio-protocol.md:315 both build the right backend:

$ uv run python -c "from rigplane.backends.factory import create_radio; from rigplane.backends.config import SerialBackendConfig; print(create_radio(SerialBackendConfig(model='IC-7610', device='/dev/cu.usbserial-111120', baudrate=115200, radio_addr=0x98, rx_device='IC-7610 USB Audio', tx_device='IC-7610 USB Audio')).model)"
IC-7610

radio-protocol.md is generic but uses IC-7610 throughout, so the added model="IC-7610" on both lan_config and serial_config is consistent. allow_low_baud_scope and model are real SerialBackendConfig fields.

2. Fragment intent is right; syntax is the defect. Each still illustrates its prose (the low-baud override fragments show allow_low_baud_scope=True; the migration fragment shows Before-LAN / After-Serial). Only the argument ordering is wrong, as above.

3. Completeness sweep at this head.

  • SerialBackendConfig(: 10 occurrences — 9 name a model (ic705:180,216; ic7610:209,346,457; troubleshooting:85,540; radio-protocol:315; tx500-audit:5). The one model-less is docs/radio-protocol.md:411, a table cell create_radio(SerialBackendConfig(...)) — illustrative ..., not a construction. No Icom serial CLI line lacks --model (see below).
  • LanBackendConfig(: 30 occurrences — 2 name a model (README.md:41, docs/radio-protocol.md:306); 28 model-less, of which docs/guide/configuration.md:73 (radio_addr=0xA4) resolves and works, and 0x42 sites + the rest raise.
  • --backend serial command lines in docs/+README.md, joined across continuations, without --model: only the yaesu-cat lines (docs/guide/cli.md:117,118, one prose line in radio-internals) — no model concept there. Zero Icom serial.
  • CLI printed strings (unchanged by this delta): epilog 5 --model lines + discover (needs none); both hint branches' printed lines carry --model; the remaining modelless printed strings are the module docstring, _warn_web_host_ambiguity and the four audio Example: strings — all LAN/reported (T213).

4. The LAN claim: the correction section is truthful. LAN still refuses without a model — measured at this head:

$ uv run python -c "from rigplane import create_radio, LanBackendConfig; create_radio(LanBackendConfig(host='192.0.2.1', username='u', password='p'))"
ValueError: Cannot resolve a radio profile: no profile, model, or matching radio_addr identifies the radio. Pass an explicit profile= or model= — rigplane no longer guesses a default rig.

So the worker's "LAN still accepts model=None" rationale is false, and the body's "Correction after review" says so ("Not fixed here, and not because they work"). The body nowhere asserts the false claim (grep model=None → none). Correct.

5. Counts. 10 files; 63+/56- = 119 reproduces (git diff origin/main...HEAD --numstat | awk '{a+=$1;d+=$2} END{print NR,a,d,a+d}'10 63 56 119); first body line still states 2 code + 8 docs = 10 files, at the hard file ceiling and not over it.

6. CI as found. gh pr checks 3416 --required: quick pass (1m35s, run on head c407b902); Agent Review Gate fail (no exact-head PASS yet — this directive would supply it, but the blocking finding above is the verdict).

REQUIRED BEFORE MERGE (prose only)

  • The correction section says the four fragments are "in the same two files". They are docs/guide/ic7610-usb-setup.md and docs/guide/troubleshooting.md; the previously-blocked pair was ic7610-usb-setup.md + radio-protocol.md. Fix the file attribution.
  • "About thirty LanBackendConfig(...) examples … identify no radio either": there are 28 model-less, and docs/guide/configuration.md:73 (radio_addr=0xA4) resolves to IC-705 and works. Narrow the sentence.
  • The guardrail first line uses 2 code + 8 docs where the template says N code + M regenerated baselines = K files; the count is right.
  • The body's census anchor (3b724c15a) and the Files table now predate c407b902; optionally refresh so the table covers the three files this commit touches.

A keyword argument followed by the ... placeholder is a SyntaxError
(positional argument follows keyword argument). Move the placeholder to
the front of the four serial config snippets and the LAN migration
snippet so every abbreviated call compiles.

Part of MOR-2425

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@morozsm

morozsm commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Review: PASS 23ac194

Head confirmed:

$ git rev-parse HEAD
23ac194ec68bb623e92e6d9a3936cbffb68dbf3e
$ gh pr view 3416 --json headRefOid -q .headRefOid
23ac194ec68bb623e92e6d9a3936cbffb68dbf3e

1. The syntax regression is fixed; I reproduce the 24/0 sweep

Compiling the exact lines read from the files (not retyped):

$ uv run python -  # reads line N from the file, compile(expr, 'eval')
docs/guide/ic7610-usb-setup.md:346  VALID   SerialBackendConfig(..., model="IC-7610", allow_low_baud_scope=True)
docs/guide/ic7610-usb-setup.md:453  VALID   LanBackendConfig(..., host="192.168.1.100")
docs/guide/ic7610-usb-setup.md:457  VALID   SerialBackendConfig(..., model="IC-7610", device="/dev/cu.usbserial-111120")
docs/guide/troubleshooting.md:85  VALID   SerialBackendConfig(..., model="IC-7610", allow_low_baud_scope=True)
docs/guide/troubleshooting.md:540  VALID   SerialBackendConfig(..., model="IC-7610", allow_low_baud_scope=True)

Independent sweep over docs/** + README.md: one-line config = *BackendConfig(...) snippets = 24 checked, 0 broken — I agree with the count. Broadening the definition does not change the verdict:

narrow (`^config = *BackendConfig(...)` one-liners): checked=24 broken=0
+ the one `radio_config = ...` line (docs/api/rigctld.md:84): 25 checked, 0 broken
all single-line occurrences incl. inline prose/table cells:  29 checked, 0 broken
keyword-then-`...` sequences anywhere in docs/README:         0

2. The fifth site

docs/guide/ic7610-usb-setup.md:453 now LanBackendConfig(..., host="192.168.1.100"), VALID. It sits under # Before (LAN) in the LAN→serial migration and still names only host (placeholder first for the omitted args) — moving the placeholder does not change what it teaches.

3. Prose still matches

ic7610-usb-setup.md:346 is under "Override (use with caution)" for the CI-V USB Baud Rate, so allow_low_baud_scope=True is the right story; :457 is the # After (Serial) half of the LAN→serial migration. Values still line up with the document: device="/dev/cu.usbserial-111120" (doc lines 75/88/137/211), CI-V 0x98 (line 38), IC-7610 USB Audio (lines 105/111), and model="IC-7610". troubleshooting.md:85,540 are the low-baud override section and use the doc's canonical IC-7610 example.

4. The rewritten correction section: every number verified

total LanBackendConfig constructions:        30
names a model:                                2   (docs/radio-protocol.md:306, README.md:41)
model-less:                                  28
  neither model nor address (refuse):        23
  radio_addr=0x.. :                           3   (configuration.md:73=0xA4; configuration.md:78=0x42; guide/radios.md:203=0x42)
  variable radio_addr:                        2   (api/radios.md:41, guide/radios.md:191)
  • "twenty-three … and two pass radio_addr=0x42; all twenty-five refuse": confirmed — bare no-address and 0x42 both raise ValueError: Cannot resolve a radio profile … no longer guesses a default rig.
  • "one resolves by address (0xA4 → IC-705)": confirmed (create_radio(LanBackendConfig(..., radio_addr=0xA4))IcomRadio, model IC-705).
  • "two pass a variable address": api/radios.md:41, guide/radios.md:191 (radio_addr=addr).
  • 23 + 2 + 1 + 2 = 28 model-less, + 2 named = 30 ✓. The worker's "LAN still accepts model=None" rationale is false and the section says so.

5. Counts and ceiling

git diff --name-only origin/main...HEAD | wc -l10. First body line still reads … + 8 docs = 10 files, at the hard file ceiling and not over it.

6. CI as found

gh pr checks 3416 --required: quick pass (1m36s, run on head 23ac194e); Agent Review Gate fail (no exact-head PASS yet — this directive supplies it).

REQUIRED BEFORE MERGE (prose only)

  • The Guardrails census still says 63+/56- = 119 under git diff --numstat origin/main...HEAD. At this head the shown command yields 64+/57- = 121 (10 63 56 119 was the c407b902 figure). Still far under the 1000-line ceiling; update the numbers, or anchor them explicitly to c407b902.
  • Guardrail first-line wording 2 code + 8 docs vs the template N code + M regenerated baselines = K files; the count is right.

@morozsm
morozsm merged commit 348ded7 into main Sep 15, 2026
10 of 17 checks passed
@morozsm
morozsm deleted the codex/docs-serial-model branch September 15, 2026 23:44
morozsm added a commit that referenced this pull request Sep 16, 2026
PR #3411 removed the factory's implicit radio guess, and PR #3416 fixed
the serial examples; the LAN Python examples were left behind, so a
reader copying one hit "Cannot resolve a radio profile" at
create_radio. A CI-V address is a factory default the operator can
change, so it does not identify a radio either: every runnable
LanBackendConfig construction in docs/ and README.md now names its
model (README already did).

Each distinct rewritten shape was executed through create_radio and
returns an IcomRadio; the pre-fix shapes were executed and raise
ValueError.

Two preset examples pass radio_addr from get_civ_addr() deliberately
and are unchanged. docs/api/rigctld.md and
docs/guide/ic7610-usb-setup.md remain for a second PR (10-file
ceiling).

Part of MOR-2425

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant