Skip to content

v3.14.0-beta.10#172

Merged
kwschulz merged 32 commits into
mainfrom
feature/v3.14.0-beta
Jun 5, 2026
Merged

v3.14.0-beta.10#172
kwschulz merged 32 commits into
mainfrom
feature/v3.14.0-beta

Conversation

@kwschulz

@kwschulz kwschulz commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Test plan

  • CI green on branch (all jobs including security scan)
  • Post-merge CI green on main
  • Tag v3.14.0-beta.10 on main, push
  • PyPI publish completes
  • HACS picks up release

🤖 Generated with Claude Code

kwschulz and others added 30 commits June 1, 2026 22:27
Schema updates since alpha.15: channel_number on all channels,
channel_identity in config_entry, stale_session_recovery fields,
last_poll_at (ISO), last_stub_body, rate_corrected/rate_uncorrected,
tcp_latency_ms. Verified on production hardware 2026-05-29.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ookie pattern

Adds `inject_credential_cookie: bool` to `UrlTokenAuth` for firmware that
returns an empty auth response body and expects the client to synthesize
the credential cookie from btoa(user:pass) — replicating browser JS that
calls createCookie("credential", btoa(user+":"+pass)) after a successful
but body-less auth response.

- `auth.py`: add `inject_credential_cookie: bool = False` to `UrlTokenAuth`
- `url_token.py`: inject btoa credential as `cookie_name` cookie when field
  is set and no body token was extracted; generalize modem-specific comment
- `http.py`: append session cookie names and basic_auth flag to the
  LoginPageDetectedError warning — surfaces at WARNING without debug mode
- `modem-basic.yaml`: wire `inject_credential_cookie: true` for the SB8200
  v6 client-side cookie auth pattern; correct notes to reflect actual flow
- `MODEM_YAML_SPEC.md`: document `inject_credential_cookie` field
- Tests: parameterize inject_credential_cookie cases; extract all inline
  credential literals and token values to module-level constants; add
  empty-body fixture; generalize fixture descriptions to patterns not models

Related to #170

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n url_token auth

Adds intake pipeline awareness of the inject_credential_cookie pattern
introduced in Core (bc464af). When analyze_har detects url_token auth
with an empty response body and a credential cookie present in post-login
requests but never issued via Set-Cookie, it now:

- Emits a named WARNING for the empty auth response body
- Runs _detect_client_side_cookie to identify the client-side origin cookie
- Emits inject_credential_cookie: true + cookie_name in AuthDetail fields
  when the pattern is confirmed (confidence: low — btoa value unverifiable
  from sanitized HAR)

Supporting changes:
- _collect_server_cookie_names extracted to keep _detect_client_side_cookie
  within complexity budget
- WARNING_PREFIX imported; unused has_content import removed
- ONBOARDING_SPEC Phase 3 table: add row for client-side cookie origin pattern
- Two fixtures cover the empty-body warning and full pattern detection cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cture

Verified by @dggrzesiak on 3.14.0-beta.8 hardware (HW 2.02, SW V10.01.07,
Comcast/Xfinity, 32 QAM + 1 OFDM DS + 4 ATDMA US locked, all signals
nominal). Refreshes modem.verified.json; flips modem.yaml status
awaiting_verification -> confirmed.

Adds skills/ folder (modem-confirm, modem-intake, soak-analysis) with a
VS Code open hook to sync into .claude/skills/. Adds comprehensive cSpell
word list. Updates MODEM_INTAKE_WORKFLOW.md to include catalog README
regeneration as Step 10 and renumbers confirmation steps to 13-16. Revises
CLAUDE.md README rule to reflect skill-driven regeneration flow.

Related to #104

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… value

The server returns a session token in the auth response body; browser JS
sets it as the credential cookie (createCookie("credential", result)).
Core now replicates this correctly. An empty auth response body is an
error — there is no btoa(user:pass) fallback.

- url_token.py: set cookie_name from body; fail if body is empty
- MODEM_YAML_SPEC.md: correct inject_credential_cookie description
- modem-basic.yaml: correct notes
- modem-basic.har: patch auth response body for golden file test
- modem-basic.expected.json: correct _about
- catalog_tools http.py: correct detection warning and docstring
- test_url_token.py: update tests; use "pw" not "password"
- test_form.py, test_form_nonce.py, test_form_pbkdf2.py, test_hnap.py:
  use "pw" for mock server credential consistency
- catalog_reference.py: hard import Core display labels (no silent
  fallback); fix comment; move import to top of file
- CHANGELOG.md: add [Unreleased] entry; correct beta.7 description
- README.md: regenerate (pre-existing badge label drift from venv/system
  Python inconsistency, now resolved)
- CLAUDE.md: update regenerate instruction

Related to #170

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move feature/* trigger into validate.yaml (canonical HACS workflow)
and remove the duplicate validate job from tests.yml. Drop the
single-item Python matrix from the test job — pinned to 3.12 to
match HA's supported floor, documented inline. Collapse the two
pytest runs into one with coverage. Add job name and reference URL
to hassfest.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
XB7 firmware emits <th>Label</td> where the label cell is closed with
</td> instead of </th>. html.parser (via BS4) nests the sibling <td>
data cells inside the unclosed <th>, causing _build_label_map to see
1 cell per row and log "No data rows found in transposed table".

Add loaders/html_normalize.py with _UNCLOSED_TH_RE and apply it in
har.py and http.py before BS4 parsing, so all parsers receive
well-formed input. Add a defense-in-depth fallback in _build_label_map
for the nesting case with a comment explaining the dependency on
html.parser behavior. Apply the same normalization in catalog_tools
html_parsing.py so the intake pipeline handles the pattern too.

Restore the XB7 HAR fixture with a complete auth flow (POST /check.jst
-> 302 + real firmware HTML on GET /network_setup.jst) and regenerate
the golden file from Eli's hardware data (34 DS + 5 US channels).
Add paired unit fixtures: one exercises the BS4 nesting fallback
directly; the other applies normalize_html() first and exercises the
normal len(cells) >= 2 path.

Remove the synthetic login-route fallback from test_harness/auth/form.py
(contradicted the validate_har intake contract) and 12 unjustified
# noqa: ARG001 suppressions across auth handlers and conftest files
(ARG001 is not in the project ruff select list).

Move cSpell word list from .vscode/settings.json to cspell.config.yaml
with category comment groupings preserved. Add regression_output.txt
to .gitignore (local stdout redirect, trend monitored via CI artifact).
Add cspell.config.yaml to check-local-path-refs.sh exempt list
(ignorePaths entries are glob patterns, not local path dependencies).

Related to #107

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docsIfUpChannelType (DOCS-IF-MIB, RFC 4546) has no OFDMA value — it
covers SC-QAM types only (TDMA, ATDMA, S-CDMA). OFDMA upstream channels
are managed by DOCS-IF31-MIB which defines no channel type field. Both
XB6 and XB7 firmware render the inapplicable SC-QAM field ("TDMA") for
their OFDMA upstream channel, causing it to be classified as atdma.

Replace the Channel Type row mapping in both upstream parser.yaml
sections with a channel_type block derived from the Modulation field
(QAM -> atdma, OFDMA -> ofdma), matching the approach already used for
downstream. Regenerate golden files: XB6 ch7 and XB7 ch10 now carry
channel_type: ofdma. symbol_rate and the generic "OFDMA" modulation
label are correctly stripped by _strip_ofdm_fields since channel_type
is now properly classified.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cleans up multi-platform residue left over from an earlier attempt to
support native Windows and dev container paths in parallel.

- GETTING_STARTED.md: WSL2 + setup.sh is now the primary path; dev
  container moved to an optional section
- welcome_message.txt: removed nonexistent setup.ps1 reference
- next_steps.txt: fixed stale task names (Test: All, Test: Quick,
  HA: Start) and removed nonexistent make docker-start
- extensions.json: add cSpell to workspace recommendations (was
  only in devcontainer); suppress Makefile Tools (C++ prompts)
- settings.json: fix python.defaultInterpreterPath to point at the
  binary, not the venv directory
- CLAUDE.md: add rule to audit task-name references when tasks.json
  changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verified by @esand on 3.14.0-beta.9 (HW 2.0, SW Prod_24.2_PD,
14 days 14h uptime, 34 DS + 5 US locked, all signals nominal).

Refreshes modem.verified.json from their diagnostics; flips
modem.yaml status awaiting_verification -> confirmed. Also corrects
the BCM3390 duplication quirk note: channel 17 (primary QAM), not
channel 10, mirrors ch_id 34 OFDM. Pattern now confirmed across
two independent units.

Also bundles:
- XB6 HAR: updated fixture for beta.9 unclosed-th normalization fix
- CLAUDE.md: fold rule 6a into 6, drop Contents table
- Catalog README/AUDIT: regenerated

Related to #2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation test gate

Downstream modulation map: "256 QAM" -> "QAM256" in parser.yaml.
Upstream: replace modulation-based channel_type derivation with a
direct Channel Type row extraction ("TDMA_AND_ATDMA"/"ATDMA" ->
"atdma", "TDMA" -> "ofdma"). Bare "QAM" has no canonical constellation
and cannot be mapped — reading channel_type directly from the HTML row
avoids the intermediate field entirely.

Updates modem.expected.json to reflect both changes.

Root cause: modem-confirm skill had no test step after the status
flip. test_confirmed_modem_golden_spec_conformance only runs for
confirmed modems, so it fires for the first time at confirmation.
Adds Step 15a to MODEM_INTAKE_WORKFLOW.md requiring catalog tests
before commit.

Related to #2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Require catalog tests after status flip (Step 15a) before committing.
test_confirmed_modem_golden_spec_conformance only fires for confirmed
modems and was missed in the XB7 confirmation. Mirrors the update
already in MODEM_INTAKE_WORKFLOW.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ings

Replaces ad-hoc allow rules with a principled three-section layout:
allow (dev workflow + gh reads), deny (staging hard-block + storage
credentials), ask (commit/push/rebase/gh writes). git staging moves
from ask to deny so it cannot be approved accidentally. Each section
has a _comment key explaining the rationale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single-session firmware enforces one active session on the admin
interface. Without logout, the integration holds the session
indefinitely, locking out browser access on port 80/443.

GET /logout.html observed in contributor HAR (Issue #170). Core
calls it after each poll via session.max_concurrent: 1 +
actions.logout, then clears the session so the next poll
re-authenticates from scratch.

No other SB8200 variants are affected (max_concurrent defaults to 0).

Related to #170

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When LOAD_AUTH or LOAD_INTEGRITY fires on a modem with
session.max_concurrent: 1 and actions.logout configured, attempt a
best-effort logout before clearing the session and retrying. Releases
any stale server-side session (e.g. after an unclean HA restart) so
the immediate re-authentication can succeed.

The firmware's logout endpoint does not require credentials — the
browser itself erases the cookie before calling it (confirmed via
contributor HAR on SB8200 v6, Issue #170). The method does not touch
session cookies; that remains clear_session()'s responsibility.

Also fixes LogoutExecuted event firing before execute_action()
completes; moved to the else branch so it reflects actual success.

Updates ORCHESTRATION_SPEC, MODEM_YAML_SPEC, and RUNTIME_POLLING_SPEC
to document both logout placements and retire the stale "post-poll
only" claim. Adds TestAttemptLogoutBeforeRetry (5 cases) and two
orchestrator ordering tests.

Related to #170

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LogoutExecuted now fires in the else branch (after execute_action
succeeds), not before. The HA-level test was asserting both
LogoutExecuted and LogoutFailed on exception — encoding the
pre-existing log-before-action bug. Updated to assert LogoutFailed
only and explicitly assert LogoutExecuted is absent on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
normalize_html() is called before BeautifulSoup at the loader input
boundary but was absent from the spec. Added step 5 update and a
follow-up paragraph (matching the SSL handling note style) describing
the unclosed-<th> firmware fix.

Also added five missing cspell words surfaced by the spec edit:
Moto, cablemodem, cmstatus (modem firmware identifiers), fromstring
(defusedxml method), artefacts (British spelling).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rsion-pinned prose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…llowup)

Replace stale [MODEL]-tag-pattern guidance in CODE_REVIEW.md and
CLAUDE.md with pointers to LOGGING_SPEC.md, which is now the
authoritative reference for the typed event taxonomy that shipped
in P42 (01a8b2c / 269b601). Add cross-references in
ORCHESTRATION_SPEC.md's two logging-contract paragraphs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the "alpha is a development concept only" sentence from
RELEASING.md; the table already covers the two shipping tiers.
Alpha distribution via branch tracking ended when zip_release:true
landed on the default branch and was never replaced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pyyaml 6.0.0→6.0.2, requests 2.31.0→2.32.3, cryptography 41.0→44.0
match HA's package_constraints.txt pins. ruff 0.15.13→0.15.15 and
black 24.0.0→26.5.1 in dev requirements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pointer

Clarify Adding Modem Support as intent-based paths (request vs. implement)
with a direct link to MODEM_INTAKE_WORKFLOW.md for hardware contributors.
Add note that a Core gap report is the contribution — no implementation
required.

Add "Know the entity model boundary" to AI contribution rules — names the
modem-data-only constraint and points to ARCHITECTURE_DECISIONS.md so
AI-assisted contributors see it before proposing derived-analysis features.

Surface skill portability: /modem-intake references in CONTRIBUTING.md and
pull_request_template.md now include the skills/modem-intake.md path for
contributors using non-Claude-Code AI tools.

Replace generic Code Style bullets with a pointer to docs/CODE_REVIEW.md.

chore(dev): broaden Bash allow rule, add jq and shell command guidance

Simplify settings.json allow list to Bash(*) — per-command prefix rules
were creating permission interrupts for legitimate dev operations.

Update CLAUDE.md shell command generation rules: prefer jq for gh output
parsing over python3 -c multiline; use fixed /tmp/claude_parse.py for
complex logic that jq can't express.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new service that finds recorder statistics with no registered
entity — left behind by mode switches, channel rebonding (ID mode),
or prefix changes.

Default call returns a full comment-only preview of orphaned entity
IDs. Call with execute: true to purge them all directly via HA's
recorder API (async_clear_statistics), bypassing the Developer Tools
YAML editor entirely.

Adds execute tests including batch boundary (150 stats across two
internal batches of 100) and a preview-only test that asserts the
output contains no runnable YAML. Adds yaml.safe_load validation to
all non-empty orphaned result cases. Updates TROUBLESHOOTING.md with
the two-step workflow and fixes MD036/MD040 lint warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Purge logs at WARNING (permanent, irreversible). Preview logs at DEBUG
(read-only, noise at INFO level). Consistent with the logging pattern
in generate_dashboard and convert_channel_identity handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stics

The service previews or purges depending on the execute flag, so
"list_" was misleading. orphaned_statistics describes the subject
without implying read-only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
handle_list_orphaned renamed to handle_orphaned_statistics to match
the factory. services.py module docstring updated to reflect that
orphaned_statistics can purge (execute: true) — not preview-only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
orphaned_statistics service (P32), logout-before-retry fix,
SB8200 v6 Basic logout catalog fix, XB7 confirmation,
XB7 modulation canonical form normalization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
kwschulz and others added 2 commits June 4, 2026 21:02
The deferred path logs N entities but only counts data-dependent
sensors; always-created sensors are registered separately at startup.
Wording now matches the function name to avoid confusion when comparing
against the normal-path "Created N sensor entities" total.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add troubleshooting entry to RELEASING.md for the "Expected — Waiting
for status to be reported" symptom (ruleset string mismatch after a
workflow job rename). Add corresponding rule to CLAUDE.md's CI job
section so the ruleset update is part of any future job rename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kwschulz kwschulz merged commit af869fa into main Jun 5, 2026
27 checks passed
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