merge: reconcile companion cleanup with fix-general-tidy

Merge the maintainer's fix-general-tidy branch (neighbor discovery,
keygen, API endpoints, web-asset rebuild, HTTP server config/control
commands, and an independent #286 room-server push/ACK/guest fix) into
the companion cleanup branch.

Both branches fixed #286 in parallel with byte-identical push-ACK CRC
logic. In the six overlapping files the maintainer's implementation is
kept (ACL replay-detection/session helpers, encoded path-len with legacy
fallback, expected_crc/ack_timeout_s injector API, dispatcher ACK
helpers); this branch's unique companion work is preserved on top
(sender_prefix persistence + migration, boot-state hardening /
CompanionStateLoadError, MessageQueue.max_size, older-core fallbacks).

Conflict resolution took the maintainer's side across the overlap, then
fixed two integration seams the merge introduced and updated this
branch's tests to the maintainer's API:
- room_server: timeout used undefined `hops`; aligned to `path_len`.
- packet_router: PATH helper was invoked twice (maintainer's
  unconditional call plus this branch's conditional local-identity
  call); dropped the now-redundant conditional block.

Pin openhop_core to @dev (was @feature/publish-workflow-message-handling)
so this can merge to the repeater's dev; core dev carries the required
sender_prefix and PathUtils.is_valid_path_len APIs.

Full suite green (1040 passed) against openhop_core dev and
refactor/companion-housekeeping; ruff clean.
This commit is contained in:
agessaman
2026-07-07 12:56:43 -07:00
120 changed files with 1881 additions and 350 deletions
@@ -70,12 +70,12 @@ async def test_path_helper_updates_client_out_path_on_valid_decrypt():
@pytest.mark.asyncio
async def test_path_helper_registers_embedded_ack():
"""Firmware path returns embed the delivery ACK after the path
(extra_type=PAYLOAD_TYPE_ACK + 4-byte CRC); it must reach ack_received_fn
(extra_type=PAYLOAD_TYPE_ACK + 4-byte CRC); it must reach ack_received_callback
so local waiters (e.g. room server pushes) resolve."""
client = _FakeClient(pubkey=bytes([0x22]) + b"x" * 31, shared_secret=b"k" * 32)
acl = _FakeACL([client])
ack_fn = AsyncMock()
helper = PathHelper(acl_dict={0x11: acl}, ack_received_fn=ack_fn)
helper = PathHelper(acl_dict={0x11: acl}, ack_received_callback=ack_fn)
packet = _PathPacket(payload=b"\x11\x22\xaa\xbb\xcc")
# path_len(2) + path + extra_type(PAYLOAD_TYPE_ACK=3) + crc(4, LE)
@@ -100,7 +100,7 @@ async def test_path_helper_handles_encoded_path_len_with_embedded_ack():
client = _FakeClient(pubkey=bytes([0x22]) + b"x" * 31, shared_secret=b"k" * 32)
acl = _FakeACL([client])
ack_fn = AsyncMock()
helper = PathHelper(acl_dict={0x11: acl}, ack_received_fn=ack_fn)
helper = PathHelper(acl_dict={0x11: acl}, ack_received_callback=ack_fn)
packet = _PathPacket(payload=b"\x11\x22\xaa\xbb\xcc")
# path_len 0x80 (3-byte hashes, 0 hops) + extra_type ACK + crc + AES padding
@@ -121,7 +121,7 @@ async def test_path_helper_ignores_non_ack_extra():
client = _FakeClient(pubkey=bytes([0x22]) + b"x" * 31, shared_secret=b"k" * 32)
acl = _FakeACL([client])
ack_fn = AsyncMock()
helper = PathHelper(acl_dict={0x11: acl}, ack_received_fn=ack_fn)
helper = PathHelper(acl_dict={0x11: acl}, ack_received_callback=ack_fn)
packet = _PathPacket(payload=b"\x11\x22\xaa\xbb\xcc")
# extra_type 0x08 (PATH) instead of ACK: nothing to register