diff --git a/AGENTS.md b/AGENTS.md index 120414a..bbc88ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,6 +84,16 @@ A physical factory-reset button is available as the final recovery path, and los This permission removes the old assumption that physical tests must remain read-only. It does **not** remove the need for deliberate test gates, state capture, read-back or restore evidence. +### Full-coverage campaign rule + +The canonical 2026-09-21 API↔SDK audit is maintained upstream in `nr2301-api/docs/audits/api-sdk-full-coverage-2026-09-21.md`. + +For the active campaign, **`router/eng_set_usb_mode` is the only hard mutation exclusion**. Every other documented method remains an explicit coverage target, even when the upstream safety class is `DO_NOT_TEST_FOR_COVERAGE`. Those labels remain risk/planning metadata rather than permanent SDK bans. + +Credential-changing paths, administrator-password recovery, SIM PIN/PUK recovery, engineering/ADB surfaces, VPN/DDNS/TR-069 credentials, radio changes, WAN interruption, reboot, config restore and factory reset are in scope when the test has an evidence-backed request contract, required known credentials/state, an explicit gate and a recovery plan. Do not brute-force or repeatedly guess secrets or engineering credentials. + +OTA download/install is in scope but should be left until late in the ACIY.3 campaign because changing firmware may replace the protocol baseline and is not normally undone by factory reset. + ### Current hard exclusion: USB management mode Do **not** mutate the router's USB/management mode, including engineering USB-mode setters, during routine coverage work. The USB path is the current control/recovery channel and should remain available. diff --git a/CHANGELOG.md b/CHANGELOG.md index 39639c3..8df254a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- corrected the administrator authority guidance from the canonical API audit: the older 2026-08-31 direct-IP `result=4` observation is now treated as runtime/state dependent because a 2026-09-21 SDK recheck successfully completed administrator login and the full LAN/router lifecycle via `http://192.168.1.1`; the SDK still defaults to `http://zyxel.home` but no longer describes direct IP as permanently unsuitable - live-profiled `save_draft(..., message_id=)` on ACIY.3: the shipped-frontend existing-ID request returns the verified save success triple but behaves as COPY_ON_SAVE (original Draft unchanged, exactly one new Draft ID contains the replacement body); SDK documentation no longer promises in-place update semantics - completed a real external SMS E2E through the public SDK: `send()` returned the verified 0/1/0 success triple and the handset physically received the message; the handset reply appeared as a new Inbox item and `get_by_id()` returned the complete documented response field set; Inbox/Outbox bodies were observed as UTF-16BE hex with phone number/content kept out of logs - added `client.sms.get_by_id()` and `client.sms.save_draft()` from normalized public contracts; draft create/update preserves the historically live-verified wire distinction (string id/type/protocol, boolean gsm7), enforces the save success triple, and redacts message content from SDK-generated errors diff --git a/docs/integration-testing.md b/docs/integration-testing.md index 85271a4..05361ef 100644 --- a/docs/integration-testing.md +++ b/docs/integration-testing.md @@ -17,11 +17,11 @@ Current assumptions: This is broader permission than the initial read-only smoke phase, but physical testing remains gated so ordinary `pytest` and CI can never mutate a router accidentally. -## Canonical management host +## Management host / authority observations -On the tested firmware `V1.00(ACIY.3)C0`, `zyxel.home` resolves to the same management address as `192.168.1.1`, but administrator pre-auth calls are host/authority sensitive. +The SDK default remains `http://zyxel.home`, but current ACIY.3 evidence does **not** establish that hostname as the only valid administrator authority. -Physical USB A/B testing on 2026-08-31 showed: +A controlled USB A/B test on 2026-08-31 observed: ```text http://192.168.1.1 @@ -33,7 +33,11 @@ http://zyxel.home account/get_rand -> result=0, rand=<8-byte challenge> ``` -The result was independent of requests-vs-urllib transport, compact JSON/header reproduction, WebUI bootstrap and prior explicit WebUI logout. Use `http://zyxel.home` for administrator login on this firmware. Anonymous/status reads may still work through the direct IP and therefore do not prove that the direct IP is suitable for login. +That observation was reproducible in the 2026-08-31 runtime state. It is **not** a permanent firmware rule. + +During the 2026-09-21 LAN/router campaign, `zyxel.home` did not resolve on the test PC, so the session explicitly used `NR2301_URL=http://192.168.1.1`. The guarded static-DHCP cleanup and the complete destructive LAN/router lifecycle both called `client.login()` successfully and completed authenticated operations through the direct IP. + +Therefore either known authority may be usable. If one authority fails during pre-auth, trying the other is a recovery option. Preserve the raw endpoint-specific result; do not reinterpret `result=4` as a universal authority error. ## Test levels @@ -159,7 +163,7 @@ NR2301_URL=http://zyxel.home NR2301_USERNAME=admin ``` -`NR2301_URL` and `NR2301_USERNAME` use those values as defaults when omitted. +`NR2301_URL` and `NR2301_USERNAME` use those values as defaults when omitted. `http://192.168.1.1` is also physically proven as a working administrator URL in the 2026-09-21 runtime state and is a valid fallback when `zyxel.home` does not resolve. ## PowerShell read-only example diff --git a/src/nr2301/client.py b/src/nr2301/client.py index 599a687..c41e4f2 100644 --- a/src/nr2301/client.py +++ b/src/nr2301/client.py @@ -227,9 +227,11 @@ def _preauth_failure_message(base_url: str, method_id: str, result: Any) -> str: message = f"{method_id} failed with result={result!r}" if _uses_tested_direct_management_ip(base_url) and _optional_int(result) == 4: message += ( - "; tested firmware V1.00(ACIY.3)C0 returns result=4 for administrator " - "pre-auth calls addressed as 192.168.1.1, while the same router/IP " - "succeeds via the canonical management host http://zyxel.home" + "; a 2026-08-31 ACIY.3 run observed result=4 for direct-IP " + "administrator pre-auth while http://zyxel.home succeeded, but a " + "2026-09-21 recheck later completed administrator login successfully " + "through 192.168.1.1; authority behavior is state/environment " + "dependent, so try the alternative known authority if available" ) return message diff --git a/tests/test_client.py b/tests/test_client.py index 46f071e..92963f7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -56,7 +56,7 @@ def set_cookie(): } -def test_login_reports_canonical_host_hint_for_direct_ip_result4(): +def test_login_reports_state_dependent_authority_hint_for_direct_ip_result4(): session = FakeSession([ FakeResponse({"result": 4}), ]) @@ -66,8 +66,11 @@ def test_login_reports_canonical_host_hint_for_direct_ip_result4(): client.login() assert exc.value.result == 4 - assert "http://zyxel.home" in str(exc.value) - assert "192.168.1.1" in str(exc.value) + message = str(exc.value) + assert "http://zyxel.home" in message + assert "192.168.1.1" in message + assert "state/environment dependent" in message + assert "recheck later completed administrator login successfully" in message assert len(session.calls) == 1