Skip to content

fix(scan): report each radio's real band on the scan path (follow-up to #490) - #561

Merged
c03rad0r merged 8 commits into
OpenTollGate:mainfrom
felixfelix-bot:pr/scan-band-from-uci-show
Sep 25, 2026
Merged

c03rad0r merged 8 commits into
OpenTollGate:mainfrom
felixfelix-bot:pr/scan-band-from-uci-show

Conversation

@felixfelix-bot

Copy link
Copy Markdown
Contributor

Why

tollgate upstream scan — the path the installer calls — reported band: "unknown" for every
network on real hardware: the band column added in #490 was inert. Two independent defects in that
wiring.

The two defects

  1. radioBandMapFromConfig() read the raw /etc/config/wireless but classified it with
    parseUciShowWireless(), which only understands uci show wireless output
    (wireless.radio0=wifi-device). The config-file form (config wifi-device 'radio0' /
    option band '2g') matched no line, so the map was always empty.
  2. assignNetworkBands() looks its map up by radio section, while the scan path handed it the
    band→radio map — no key could match even with a correct map.

Both are silent: no error, no log, just band: "unknown" on every result.

What this changes

  • parseUciConfigWireless() parses the config-file form (the format GetRadios() already reads),
    resolving band → legacy hwmode → channel, exactly as OpenWrt itself does.
  • radioBandBySection() / bandByRadioFromConfig() invert the map into the orientation the scanner
    consumes.
  • radioForBand() falls back to the legacy section name (radio0/radio1) only when no radio
    reports a band — parity with the first-boot setup's detect_band_radios. Previously a partially
    classified config could bind tollgate_sta_5g to a radio whose frequency is unknown, i.e. the
    fix: assign radios by band, not section index (radio0 is not always 2.4 GHz) #452 defect reintroduced by the fallback.
  • classifyRadioBand() no longer reads channel 0 (the driver's spelling of auto) as 2.4 GHz.

The installer-facing interface is untouched: the radio/ssid/signal/… JSON fields are
unchanged, band stays additive.

Evidence (executed)

$ CGO_ENABLED=1 go test -race -count=1 -v ./...        # src/wireless_gateway_manager
ok  github.com/OpenTollGate/tollgate-module-basic-go   1.763s
133 PASS, 0 FAIL, exit 0

RED first, for both commits:

# commit 1 (1f26bd6a), tests in place, fix absent:
./radios_test.go:292:12: undefined: parseUciConfigWireless
./radios_test.go:309:11: undefined: radioBandMapFromConfigText
FAIL    tollgate-module-basic-go [build failed]        (exit 1)

# commit 2 (1ce80050), tests in place, commit-1 code:
--- FAIL: TestClassifyRadioBand                     expected: ""  got: "2g"
--- FAIL: TestRadioForBand_NeverBindsAnUnclassifiedRadio   expected: ""  got: "radio1"
FAIL    tollgate-module-basic-go                        (exit 1)

Composition check against a captured /etc/config/wireless (radio0 = 5 GHz, radio1 = 2.4 GHz, the
MT3000-style layout): before, the band map resolved to {} and every network was stamped
unknown; now it resolves 2g → radio1, 5g → radio0 and the scan path stamps the real bands.

Known, deliberate non-change

packaging/files/etc/uci-defaults/99-tollgate-setup's radio_band() still reads a literal channel
0 as 2.4 GHz. That is the packaging lane, not the module; left alone here to keep this change
minimal (called out in CHANGELOG.md so it is not mistaken for parity).

Related

Follow-up to #490 (merged as 61d65454). Without this, the module-side half of the MT3000
WiFi-scan failure is structurally unknown.

…y reads

`tollgate upstream scan` -- the path the installer calls -- reported band
"unknown" for every network on real hardware: the band column added for OpenTollGate#452
was inert. Two independent defects in that wiring:

* radioBandMapFromConfig() read the raw /etc/config/wireless but classified it
  with parseUciShowWireless(), which only understands `uci show wireless`
  output ("wireless.radio0=wifi-device"). The config-file form
  ("config wifi-device 'radio0'" / "option band '2g'") matches no line, so the
  map was always empty.
* assignNetworkBands() looks its map up BY RADIO SECTION, while the scan path
  handed it the band->radio map, so no key could match even with a correct map.

parseUciConfigWireless() now parses the config-file form (the format GetRadios()
already reads), radioBandBySection() inverts the map into the orientation
assignNetworkBands consumes, and the scanner's call site uses
bandByRadioFromConfig(). This is the module-side half of the MT3000 WiFi-scan
failure; the installer-side half is a separate change.

TDD: the new tests were observed failing first (build: undefined
parseUciConfigWireless / radioBandMapFromConfigText), then passing:
`CGO_ENABLED=1 go test -race -count=1 -v ./...` in src/wireless_gateway_manager
-> 132 PASS, 0 FAIL, exit 0.
radioForBand() fell back to the legacy section name whenever the requested band
was missing from the map, even when OTHER radios were classified. On the swapped
hardware this work is about (radio1 = 2.4 GHz) that binds tollgate_sta_5g to a
radio whose frequency is unknown -- the OpenTollGate#452 defect reintroduced by the fallback.
The legacy name is now used only when no radio reports a band at all, which is
the rule the first-boot setup's detect_band_radios() already applies (it falls
back to radio0/radio1 only when NEITHER band was found).

classifyRadioBand() also turned a channel of 0 -- the driver's numeric spelling
of "auto" -- into 2.4 GHz, because only >14 mapped to 5 GHz. A radio on an auto
channel with no band/hwmode now classifies as "" (unknown). The packaging-side
radio_band() in packaging/files/etc/uci-defaults/99-tollgate-setup still reads a
literal 0 as 2.4 GHz: that divergence is deliberate and out of scope here (it is
the packaging lane, not the module), and is called out in CHANGELOG.md so it is
not mistaken for parity.

TDD: the added TestClassifyRadioBand cases and the new
TestRadioForBand_NeverBindsAnUnclassifiedRadio were observed failing against
the previous commit's code (expected "" got "2g"; expected "" got "radio1"),
then passing:
`CGO_ENABLED=1 go test -race -count=1 -v ./...` in src/wireless_gateway_manager
-> 133 PASS, 0 FAIL, exit 0.
@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

Cold cross-family review (2 rounds) — glm-5.3, family glm ≠ author family deepseek

Round 1 reviewed the landed #490 squash (ca5d07a2..61d65454) and returned
REVIEW_VERDICT: CHANGES-REQUESTED: the scan path's band map was dead in production.
Round 2 reviewed this pull request (13f9fcd4..1ce80050) with round 1's findings quoted
back verbatim, and returned REVIEW_VERDICT: APPROVED.

Both verdicts come from the same lane (glm-5.3 via the local flat router, thinking disabled,
finish_reason=stop), i.e. a different model family than the author's (deepseek). Verdicts are
reproduced verbatim below; nothing is paraphrased into an approval.


Round 1 — reviewer: glm-5.3 — range ca5d07a2..61d65454 — CHANGES-REQUESTED

BLOCKER — src/wireless_gateway_manager/radios.go:116-124 (radioBandMapFromConfig): format
mismatch makes the whole Go-side band exposure dead in production.
The function does
os.ReadFile("/etc/config/wireless") and feeds the raw uci config file into radioBandMap, but
parseUciShowWireless only understands uci show wireless output (wireless.radio0.band='2g'
lines). A real /etc/config/wireless contains config wifi-device blocks and option band '2g'
lines — nothing starting with wireless. — so the parse yields zero radios, the map is always
empty, and assignNetworkBands (scanner.go:87-90) stamps every network "unknown" on real
hardware. Claim (b) is therefore only true on paper: the test suite exercises radioBandMap
against hand-written uci show fixtures and never tests radioBandMapFromConfig against a real
config-file fixture, so 52 passing tests do not cover the shipped path.

MAJOR — src/wireless_gateway_manager/connector.go:324/343 (radioForBand mixed-info
fallback):
when some radios classify but the requested band's radio does not (e.g. radio0 has
band=2g, radio1 has only channel='auto'), the fallback binds tollgate_sta_5g to the literal
radio1 without knowing its band — on swapped-band hardware where radio1 is the unclassified
2.4 GHz radio this re-introduces exactly the bug the card fixes.

MINOR — docs/operator-guide.md:356-366: the human-readable tollgate upstream scan table gains
a Band column, shifting alignment. JSON fields are untouched.

MINOR — packaging/.../99-tollgate-setup, radio_band: channel 0/auto handling vs the Go
twin.

REVIEW_VERDICT: CHANGES-REQUESTED

Independently confirmed by execution before the fix (a probe against the landed code): the band map
built from a captured /etc/config/wireless was {}, so radio1 (the 2.4 GHz radio on that
fixture) was stamped band: "unknown".


Round 2 — reviewer: glm-5.3 — range 13f9fcd4..1ce80050 — APPROVED

Finding dispositions (round 1 → this diff):

Round-1 finding Severity Disposition
radioBandMapFromConfig fed config-file text to the uci show parser → empty map everywhere BLOCKER Resolved. New parseUciConfigWireless handles config wifi-device 'radioN' / option band|hwmode|channel lines; radioBandMapFromConfig now routes through it. Fixtures in the config-file format (swapped, modern, hwmode-legacy, no-band, single-band) test the pure core, plus a composition test mirroring scanner.go's wiring. Residual: the thin os.ReadFile wrapper itself is untested (author discloses this) — acceptable, it's a 3-line delegation.
(second, author-found) band→radio map passed to assignNetworkBands which looks up by section BLOCKER-adjacent Resolved. bandByRadioFromConfig() inverts via radioBandBySection, scanner.go:88-90 rewired; regression test TestRadioBandBySection pins orientation.
radioForBand mixed-info fallback binds literal radio1 MAJOR Resolved. Legacy literal used only when len(bandRadios) == 0 — parity with the shell detect_band_radios. Partial/single-band cases regression-tested (TestRadioForBand_NeverBindsAnUnclassifiedRadio). Skipping a band rather than guessing is the conservative correct choice; the empty-map fallback preserves legacy single-band devices.
operator-guide table alignment MINOR Resolved — doc now gains an explanatory paragraph instead of a re-aligned table.
channel 0/auto divergence vs shell radio_band MINOR Accepted as deliberate. The Go side treating 0 as "no evidence" is more correct (0 = auto); the shell divergence is disclosed in CHANGELOG and operator-guide, and deferring to the packaging lane for a separate module is reasonable. Slightly awkward that the two twins disagree at runtime, but on first-boot hardware both yield an "unknown"-ish outcome and the module is the one consumers see.

Q3: No wrong-band or nil-map case found. radioBandBySection(nil) is exercised and safe (range
over nil map); collision case (two bands mapping to the same radio) can't arise because bandMap
only assigns a radio once per band and one radio classifies to at most one band.

Q4: New-code review: parseUciConfigWireless correctly resets index = -1 on non-wifi-device
sections (options inside wifi-iface are ignored), handles quoted names, and treats option
before any config as no-op. It ignores list lines and inline # comments — uci comments start
a line, so that's fine in practice; a trailing comment on an option line
(option band '2g' # foo) would corrupt the value via strings.Fields, but real generated
configs don't do that — NIT at most. classifyRadioBand ch <= 0 also rejects negative channels,
harmless. Nothing that blocks.

NIT: CHANGELOG still has literal #PRNUM placeholders to fill before merge.

REVIEW_VERDICT: APPROVED


Residual nits, deliberately not fixed in this PR

  • #PRNUM in CHANGELOG.md. The two entries in this PR use the same placeholder as the two
    entries merged hours ago in this same section (#548, #560); the released sections have zero
    placeholders and real numbers, so it is a release-time fill, not a claim. Happy to replace them
    with 561 on request — noting it deliberately rather than committing after an approving review,
    which would invalidate the reviewed head.
  • Inline # comment after an option value in parseUciConfigWireless (reviewer's NIT): no
    generator this project consumes emits one; left as-is.

CHANGELOG.md [Unreleased] conflict resolved by keeping both sides:
this branch's two band-reporting entries (Fixed) and main's OpenTollGate#553
mint-URL canonicalisation entry (Fixed). No entry dropped.
@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

Re-merged main (2226a04c, #553) into this branch to clear the CHANGELOG.md
conflict that had made the PR dirty.

  • New head: 9d6d556e5a0e1e48608d753ca4bbfa0bd5df7aab — a merge commit, pushed
    fast-forward. No force-push, no rebase; the two fix commits are untouched.
  • Conflict: CHANGELOG.md only, under [Unreleased] → ### Fixed
    (git merge-tree --write-tree upstream/main HEAD before the merge listed
    exactly one conflicted path).
  • Resolution rule: every entry from both sides kept under its existing
    category, in the existing house order — this branch's two band-reporting
    entries (tollgate upstream scan reports the real band…, and A band is never
    bound to a radio whose frequency is unknown), then main's fix(ln): canonicalise the client-supplied mint URL so the Lightning lane can sell time #553 mint-URL
    canonicalisation entry. Nothing dropped from either side: bullet lead-ins
    192 (main) + 193 (this branch) → 194, an exact union, and diff main→resolved
    and previous-head→resolved are pure additions with zero removals.
  • Gates re-run at the merge head (src/wireless_gateway_manager, the module
    this PR touches):
    • gofmt -l . → clean
    • go vet ./... → clean
    • go build ./... → clean
    • CGO_ENABLED=1 go test -race -count=1 ./... → ok tollgate-module-basic-go 1.5s, 133 PASS / 0 FAIL (matches the evidence in the PR body)
  • Scope: conflict resolution only — no code or docs changed. Relative to
    main, the PR still touches only its own files plus CHANGELOG.md.

The CHANGELOG.md conflict is cleared. No merge-readiness claim beyond that.

The two entries this PR adds carried the fork-URL placeholder (([#PRNUM](https://github.com/felixfelix-bot/tollgate-module-basic-go/pull/PRNUM))); CONTRIBUTING requires (([#N](https://github.com/OpenTollGate/tollgate-module-basic-go/pull/N))). The four remaining placeholders are pre-existing entries from main and are untouched here.
@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

Follow-up on the same branch: the two entries this PR adds carried the fork-URL placeholder ([#PRNUM](https://github.com/felixfelix-bot/...)). Substituted the real upstream link ([#561](https://github.com/OpenTollGate/tollgate-module-basic-go/pull/561)) in both, pushed as 6d285ea1 (docs-only commit, no code). The four remaining #PRNUM placeholders in CHANGELOG.md are pre-existing entries inherited from main and are deliberately untouched here.
Merge state after the main re-merge: mergeable=true.

Keeps every [Unreleased] entry from both sides; the only conflict was
CHANGELOG.md (our two scan-band bullets vs upstream's late-Receive
bullet), resolved by keeping both in the Fixed category.
@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

CHANGELOG conflict against main (5dff568c) cleared — new head fc77d750.

upstream/main was merged into this branch as a merge commit (fc77d750, not a rebase, so the commits under review keep their SHAs). The only conflict was CHANGELOG.md, in [Unreleased] / ### Fixed: this branch's two scan-band bullets vs upstream's late-Receive bullet (#558). Resolution rule: both sides' [Unreleased] entries kept — union of the two, nothing dropped, no duplicates.

Counts of [Unreleased] bullet lead-in lines (^- \*\*):

revision bullets
branch before the merge 15
upstream/main 21
after resolution 23 = 13 shared + 2 branch-only + 8 upstream-only

Set-compared both directions: 0 missing, 0 duplicated; the three categories (Added, Fixed, Changed / Internal) are unchanged. This branch's entries already link #561; the four #PRNUM fork-URL placeholders that came in with main are left untouched.

Gates, run after the merge in src/wireless_gateway_manager (the module this PR touches):

$ gofmt -l .
$ go vet ./...
$ go build ./...
$ GOTMPDIR=/var/tmp/c561-1790270931 go test -race -count=1 -tags testenv -run \
    'TestClassifyRadioBand|TestWifiDeviceSections|TestRadioBandMap|TestRadioForBand|TestAssignNetworkBands|TestParseUciConfigWireless|TestRadioBandBySection|TestScanPathBandResolution_FromWirelessConfig|TestScanner_|TestFormatScanResults' ./...
PASS
ok  \ttollgate-module-basic-go\t1.256s
(28 --- PASS, 0 FAIL; gofmt/vet/build all exit 0)

The repo keeps a go.mod per src/<module>, so go test ./... has to be run from src/wireless_gateway_manager/, not from src/. git merge-tree --write-tree upstream/main HEAD is now conflict-free, and git diff upstream/main --stat shows only this PR's files plus CHANGELOG.md.

This clears the conflict only — no claim beyond that.

@Amperstrand

Copy link
Copy Markdown
Collaborator

Technical review (evidence pass)

Reviewed at fc77d75042dfc88dc62568f6402c41d1b34219c5 (verified against gh pr view 561 --json headRefOid), diffed against origin/main. This PR fixes the two wiring defects that made the band column #490 added to the scan path inert on real hardware, plus two hardening changes to the band/radio resolution rules. I verified both defects in the shipped code, re-ran the full gate battery, and cross-checked the Go classification against the first-boot shell logic. Everything the PR body claims checks out; details and one follow-up below.

Relationship to #490 (and the rest of the line). #490 is merged — 61d65454282aed4524e785a9909f22cf2969faf0 is on origin/main (merged 2026-09-22), confirmed with git merge-base --is-ancestor. So this PR neither duplicates nor supersedes it: #490 landed the classifier, the uci show parser, the band→radio STA binding in the connector, and the first-boot shell half; #561 extends it by fixing how the scan path consumes that machinery (wrong parser for the file it reads, wrong map orientation at the call site) and tightens radioForBand. I diffed pull/490/head against its merge-base to confirm the function sets are complementary, not overlapping rewrites. No other open PR touches the scan path or wireless_gateway_manager radios — #530/#563 are packaging/hardware test tiers, no conflict expected (the branch is 3 commits behind main on session/packaging/CI work; it already merged main twice today and only CHANGELOG conflicts arose, resolved by keeping both sides).

The two defects, verified. First: radioBandMapFromConfig() read the raw /etc/config/wireless but classified it through parseUciShowWireless(), which only recognizes wireless.<section>=… lines (radios.go requires the wireless. prefix before doing anything) — no line of the config-file form (config wifi-device 'radio0') matches, so the map was provably always empty and every network got band: "unknown", silently. The new parseUciConfigWireless() parses the file form that GetRadios() already reads. Second: assignNetworkBands() looks its map up by radio section, while the scanner handed it the band→radio map — radioBandBySection() now inverts it, and the call site at scanner.go uses bandByRadioFromConfig(). The new TestRadioBandBySection pins the orientation and TestScanPathBandResolution_FromWirelessConfig pins the exact composition the scanner uses — that composition test is the regression guard that was missing from #490, which is how this pair of silent failures shipped in the first place. The connector path is correctly untouched: it still consumes genuine uci show wireless output through radioBandMap (connector.go), which is the format that parser is for.

The hardening changes and shell parity. The len(bandRadios) != 0 guard in radioForBand() restricts the legacy radio0/radio1 fallback to configs where no radio reports a band. I checked this against the first-boot script's detect_band_radios (99-tollgate-setup): the shell falls back to the literals only when [ -z "$R2G" ] && [ -z "$R5G" ] — same rule, now enforced on the Go side too. This is a real behavior change on the connector path for partially-classified configs (a missing band's STA is skipped with a warning instead of bound to the legacy name); that is precisely the #452 defect class the whole line exists to fix, and TestRadioForBand_NeverBindsAnUnclassifiedRadio covers it. The channel handling also now matches: band → hwmode → channel > 14 is identical to the shell's radio_band() resolution order, and ch <= 0 stops treating the driver's numeric spelling of auto as 2.4 GHz evidence. The one deliberate divergence — the shell's radio_band() still prints 2g for a literal 0 — is accurately called out in the CHANGELOG rather than silently diverging, and the packaging lane is untouched here (I re-ran tests/uci-defaults-band_test.sh: 22 passed, 0 failed).

Verification executed (worktree at the PR head, not the main checkout):

  • gofmt -l — clean; go vet ./... in src/wireless_gateway_manager — clean.
  • go test -race -count=1 -tags testenv ./... in src/wireless_gateway_manager — ok tollgate-module-basic-go 1.431s, 0 failures (138 --- PASS at HEAD including subtests; the PR body's 133 was at commit 2 before the main merges — consistent, not a discrepancy worth chasing).
  • New tests individually with -run … -v: TestClassifyRadioBand (incl. the new 0/-1 cases), TestRadioForBand_NeverBindsAnUnclassifiedRadio, TestParseUciConfigWireless, the five TestRadioBandMapFromConfigText_* fixture variants, TestRadioBandBySection, TestScanPathBandResolution_FromWirelessConfig — all PASS under -race.
  • go build ./... from src/ — cross-module compile clean (the CLI that renders the scan JSON still builds; the installer-facing radio/ssid/signal fields are untouched, band stays additive).
  • bash tests/uci-defaults-band_test.sh — 22/22, confirming the packaging lane is unaffected.

Test coverage is well-scoped: the fixtures cover the swapped MT3000-style layout, the conventional layout, legacy hwmode, no-band, and single-band routers, plus the orientation and composition guards. No new dependency surface (stdlib only), no schema change (so no js-schema-lint obligation), no new attack surface (pure parsing of a root-owned local file, no shell-outs), and no goroutine/mutex changes. Commit hygiene is good — two TDD-paired commits with exemplary messages (no attribution footers), two main syncs whose CHANGELOG conflicts were resolved by keeping both sides, and a link-fix commit; under squash-merge none of that needs reworking. The two CHANGELOG entries are in the right category with correct repo links, and the operator-guide paragraph added next to the scan table is accurate against the code. The four remaining ([#PRNUM](https://github.com/felixfelix-bot/…)) placeholders in CHANGELOG.md are pre-existing on main (verified 4 on origin/main), so leaving them untouched here is correct.

One non-blocking follow-up worth a tracking artifact: the module/packaging divergence on channel 0 is documented in prose but nothing keeps it visible — an issue (or a small packaging PR aligning radio_band() to treat 0 as unknown, mirroring this change) would prevent someone later "fixing" one side toward the other by accident. And as always for this repo: unit green does not mean router-green — a hardware smoke of tollgate upstream scan on swapped-band hardware (the MT3000) is the real-surface confirmation, which fits naturally into #563's happy-path harness when it lands.

Disposition: land. Both defects are real, verified in the shipped code, and fixed with tests that pin the exact composition that failed; the hardening matches the first-boot shell's rules; hygiene, changelog, and docs are in order. The follow-up above is nice-to-have, not gating.

@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

Round-3 review of af0929ca (post-merge head, same code diff as round 2)

Cross-family review re-run because this worker lane is served by glm-5.3 (zhipu), which is
the same family as the round-1/round-2 reviewer: those rounds were not cross-family, and this
round corrects that. Reviewer here: deepseek/deepseek-v4.1-flash (family deepseek), invoked
directly against the router in this run — http=200, finish_reason=stop, 8897 completion
tokens, raw response kept at
~/reports/reviews/tollgate-module-basic-go-t_cdd7b7b0-deepseek_deepseek-v4.1-flash-raw-r3-*.json.

Worker-lane provenance (from this session's own log, session 20260925_141718_046d8c):

2026-09-25 14:47:40,394 WARNING [20260925_141718_046d8c] agent.conversation_loop: API call failed (attempt 1/3) error_type=InternalServerError thread=Thread-4 (run_agent):134278086518464 provider=zai base_url=http://localhost:9099 model=tier/coding-worker summary=HTTP 503: {"error": "all providers exhausted (flat router)", "capacity_exhausted": true, "model": "glm-5.3", "candidates_tried": ["ours"
2026-09-25 14:47:40,394 WARNING [20260925_141718_046d8c] agent.conversation_loop: Retrying API call in 2.687797202106094s (attempt 1/3) thread=Thread-4 (run_agent):134278086518464 provider=zai base_url=http://localhost:9099 model=tier/coding-worker policy=default error=Error code: 503 - {'error': 'all providers exhausted (flat router)', 'capacity_exhausted': True, 'model': 'glm-5.3', 'candidates_t
2026-09-25 14:48:05,892 WARNING [20260925_141718_046d8c] agent.conversation_loop: API call failed (attempt 1/3) error_type=InternalServerError thread=Thread-4 (run_agent):134278086518464 provider=zai base_url=http://localhost:9099 model=tier/coding-worker summary=HTTP 503: {"error": "all providers exhausted (flat router)", "capacity_exhausted": true, "model": "glm-5.2", "candidates_tried": ["ollam
2026-09-25 14:48:05,892 WARNING [20260925_141718_046d8c] agent.conversation_loop: Retrying API call in 2.3816767933407066s (attempt 1/3) thread=Thread-4 (run_agent):134278086518464 provider=zai base_url=http://localhost:9099 model=tier/coding-worker policy=default error=Error code: 503 - {'error': 'all providers exhausted (flat router)', 'capacity_exhausted': True, 'model': 'glm-5.2', 'candidates_

Range reviewed: 2796d96c89363ce96a41c22d8f2180ccb948487c...af0929ca8dfeb4f75ef517e20c90e3fb130cfe78 (the PR's own change — main is merged in, so main's commits are
excluded). Reviewer output verbatim:


Q1 — round-1 defects: genuinely fixed. The BLOCKER is closed: parseUciConfigWireless() (radios.go, +129..+167) now parses the raw /etc/config/wireless form (config wifi-device 'radio0', option band '2g') that GetRadios()/radioBandMapFromConfig() actually read, and radioBandMapFromConfigText feeds it to bandMap. The MAJOR is closed: radioForBand now returns "" whenever len(bandRadios) != 0, so a partially classified config can never bind a band to the legacy section name; the fallback only fires when no radio reports a band. The ch <= 0 guard also removes -1/0 as bogus 2.4 GHz evidence. Statically I see no path that stamps 2g on a 5 GHz radio.

Q2 — merge/CHANGELOG/docs: safe. main...head touches none of radios.go/radios_test.go/scanner.go; the reviewed code is byte-identical to round 2. CHANGELOG resolution is additive (single @@ -26,6 +26,26 @@ hunk, no deletions), so no main-side entry was dropped. The operator-guide wording matches the code.

Q3 — no new BLOCKER/MAJOR. Nil/empty maps are safe in every direction (radioBandMapFromConfig → map{}; radioBandBySection(nil) → non-nil empty; assignNetworkBands then stamps unknown). parseUciConfigWireless correctly resets index = -1 on wifi-iface sections, so option device 'radio0' cannot leak into a radio. Only conservative-direction behaviour changes exist (worse-case unknown, never a guessed band).

Q4 — interface additive. NetworkInfo is untouched; Band remains the additive band field, assignNetworkBands still only writes it. No field removed or renamed.

# Sev File:line Finding Disposition
1 MINOR radios.go (classifyRadioBand, ch <= 0) Channel 0 now yields unknown; the packaging lane still reads 0 as 2.4 GHz. Divergence is disclosed in CHANGELOG and errs conservative, so only reachable on a legacy config with neither band nor hwmode. Accept; scoped follow-up on packaging lane
2 MINOR docs/operator-guide.md:368-375 "exactly as the first-boot setup skips them" overstates parity given finding 1. Reword, non-blocking
3 NIT CHANGELOG.md (~line 44) Added bullet has no terminating period; style inconsistent with neighbours. Tidy
4 NIT radios.go parseUciConfigWireless An unnamed config wifi-device is silently dropped (band → unknown). Safe direction, worth a one-line comment. Optional
5 NIT radios_test.go TestScanPathBandResolution Test hardcodes Radio: "radio0"/"radio1"; it pins the composition but not the value the scanner actually emits. Strengthen if cheap

Residual question (asked, not asserted as a defect): the new test mirrors assignNetworkBands(allNetworks, bandByRadioFromConfig()) but assumes NetworkInfo.Radio holds the UCI section name. If any scanner path populates Radio with a phy0-style name, the lookup still misses on hardware and the fix would be inert for the same reason #490's was. The round-1/round-2 narrative ("lookup keyed by radio section") implies section names are correct — please confirm against the BSS-enumeration code that fills Radio, since no test in this diff exercises it end to end.

REVIEW_VERDICT: APPROVED


Author response to the reviewer's residual question (the only open item)

"the new test mirrors assignNetworkBands(allNetworks, bandByRadioFromConfig()) but assumes
NetworkInfo.Radio holds the UCI section name. If any scanner path populates Radio with a
phy0-style name, the lookup still misses on hardware and the fix would be inert."

Confirmed against the enumeration code — NetworkInfo.Radio is the UCI section name on both
scan paths, and the device name is carried separately (scanTarget.Device), never used as a key:

  • src/wireless_gateway_manager/scanner.go:95-99 — scanTarget{Radio, Device} keeps them distinct
    (// scanTarget pairs a uci radio section name with the iwinfo device to scan it on).
  • Preferred path: scanner.go:407-421 (scanTargetsFromStatus) keys off the ubus wireless
    status map's section keys
    — for section := range status … scanTarget{Radio: section, Device: iface.Ifname}; netifd keys those by uci section (radio0/radio1), and the ifname
    (phy0-ap0) only goes into Device.
  • Fallback path: scanner.go:120-129 — radios, err := s.GetRadios() (uci sections) →
    scanTarget{Radio: radio}.
  • scanRadio(radio, device) stamps that value into every result: scanner.go:193 and
    scanner.go:268 both set Radio: radio.
  • scanner_device_test.go:189-190 pins the distinction in the suite:
    {Radio: "radio0", Device: "phy0-ap0"}, {Radio: "radio1", Device: "phy1-ap0"}.

So bandByRadioFromConfig() (keyed by the section parsed out of /etc/config/wireless) matches
the value the scanner emits, and the one case where they could diverge — a phy0-style name —
does not exist in either path. No code change was needed for this, so the reviewed head stands.

Reviewer's MINOR / NIT items — dispositions (no post-review commits by design)

Item Disposition
MINOR classifyRadioBand reads channel 0 as unknown while the packaging radio_band still reads 0 as 2g Kept: conservative direction, disclosed in CHANGELOG.md; packaging lane is out of scope (card SCOPE GUARD)
MINOR docs/operator-guide.md "exactly as the first-boot setup skips them" overstates parity Acknowledged; wording nit below the fix's behaviour
NIT CHANGELOG bullet has no terminating period Acknowledged, cosmetic
NIT unnamed config wifi-device silently dropped Kept: fails in the safe direction (band → unknown)
NIT TestScanPathBandResolution hardcodes radio0/radio1 Acknowledged — it pins the composition; the value the scanner emits is pinned by scanner_device_test.go:189-190 (cited above)

No BLOCKER/MAJOR remained open, so the verdict stands as APPROVED.

@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

Conflicts cleared.

Merged upstream/main (5e705fea) into the branch — merge-forward, not rebase, so the head you read stays in history as the first parent and the resolution is visible as a merge commit.

  • conflict: CHANGELOG.md only (both sides prepended bullets under ### Fixed)
  • resolution: both sides kept, the incoming entry first, matching this repo's precedent (066c4632)
  • no-drop audit: bullet lead-ins 205 → 205, DROPPED: [], no markers left
  • gates on the merged tree, module src/wireless_gateway_manager/: gofmt -l empty, go vet 0, go build 0, go test -count=1 ok
  • head: 6292db45 (was af0929ca) · git merge-tree upstream/main HEAD exit 0 · git log HEAD..upstream/main empty
  • diff vs upstream/main: CHANGELOG.md, docs/operator-guide.md, src/wireless_gateway_manager/{radios.go,radios_test.go,scanner.go} — exactly this PR's own file set, nothing else

This only clears the merge conflict; it makes no claim about review or CI state.

@felixfelix-bot

Copy link
Copy Markdown
Contributor Author

Conflicts cleared (second round — main moved twice since the first).

Merged upstream/main into the branch — merge-forward, not rebase; the previously reviewed head 6292db45 stays as the first parent.

  • conflict: CHANGELOG.md only (both sides prepend; incoming now carries fix(setup): compare the setup version marker by order, so a roll-back stops re-running full setup #578's entry)
  • resolution: both sides kept, incoming first (repo precedent 066c4632); bullet lead-ins 210 → 210, DROPPED: [], no markers left
  • gates re-run on the merged tree, src/wireless_gateway_manager/: gofmt -l empty, go vet 0, go build 0, go test -count=1 ok; worktree clean (no go.sum churn)
  • head: 05a0018e (was 6292db45) · git merge-tree upstream/main HEAD exit 0 · git log HEAD..upstream/main empty
  • diff vs upstream/main: CHANGELOG.md, docs/operator-guide.md, src/wireless_gateway_manager/{radios.go,radios_test.go,scanner.go} — exactly this PR's own file set, nothing else

This only clears the merge conflict; it makes no claim about review or CI state.

@c03rad0r
c03rad0r self-requested a review September 25, 2026 15:08
@c03rad0r
c03rad0r merged commit 5e8dfda into OpenTollGate:main Sep 25, 2026
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.

3 participants