Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<existing>)` 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
Expand Down
14 changes: 9 additions & 5 deletions docs/integration-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
8 changes: 5 additions & 3 deletions src/nr2301/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 6 additions & 3 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}),
])
Expand All @@ -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


Expand Down
Loading