import asyncio from starlette.requests import Request def _fresh_persona_state(tmp_path, monkeypatch): from services.mesh import mesh_secure_storage, mesh_wormhole_identity, mesh_wormhole_persona monkeypatch.setattr(mesh_secure_storage, "DATA_DIR", tmp_path) monkeypatch.setattr(mesh_secure_storage, "MASTER_KEY_FILE", tmp_path / "wormhole_secure_store.key") monkeypatch.setattr(mesh_wormhole_persona, "DATA_DIR", tmp_path) monkeypatch.setattr(mesh_wormhole_persona, "PERSONA_FILE", tmp_path / "wormhole_persona.json") monkeypatch.setattr( mesh_wormhole_persona, "LEGACY_DM_IDENTITY_FILE", tmp_path / "wormhole_identity.json", ) return mesh_wormhole_persona, mesh_wormhole_identity def _request(path: str) -> Request: return Request( { "type": "http", "headers": [], "client": ("test", 12345), "method": "POST", "path": path, } ) def test_transport_identity_is_separate_from_dm_identity(tmp_path, monkeypatch): persona_mod, identity_mod = _fresh_persona_state(tmp_path, monkeypatch) dm_identity = identity_mod.bootstrap_wormhole_identity(force=True) persona_state = persona_mod.bootstrap_wormhole_persona_state(force=True) transport_identity = persona_state["transport_identity"] assert dm_identity["node_id"] assert transport_identity["node_id"] assert dm_identity["node_id"] != transport_identity["node_id"] assert dm_identity["public_key"] != transport_identity["public_key"] def test_gate_anonymous_session_differs_from_transport_identity(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) transport_identity = persona_mod.get_transport_identity() gate_identity = persona_mod.enter_gate_anonymously("journalists", rotate=True) assert gate_identity["ok"] is True assert gate_identity["identity"]["scope"] == "gate_session" assert gate_identity["identity"]["gate_id"] == "journalists" assert gate_identity["identity"]["node_id"] != transport_identity["node_id"] def test_gate_access_proof_prefers_rotating_session_identity_over_persistent_persona(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) session = persona_mod.enter_gate_anonymously("journalists", rotate=True)["identity"] persona = persona_mod.create_gate_persona("journalists", label="source-a")["identity"] import main proof_identity = main._resolve_gate_proof_identity("journalists") assert proof_identity is not None assert proof_identity["scope"] == "gate_session" assert proof_identity["node_id"] == session["node_id"] assert proof_identity["node_id"] != persona["node_id"] def test_gate_access_proof_auto_enters_gate_when_identity_missing(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) import main proof = asyncio.run( main.api_wormhole_gate_proof( _request("/api/wormhole/gate/proof"), main.WormholeGateRequest(gate_id="journalists"), ) ) active = persona_mod.get_active_gate_identity("journalists") assert proof["ok"] is True assert proof["gate_id"] == "journalists" assert active["ok"] is True assert active["source"] == "anonymous" assert active["identity"]["scope"] == "gate_session" assert active["identity"]["node_id"] == proof["node_id"] def test_gate_identities_are_separate_from_root_identity(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) state = persona_mod.read_wormhole_persona_state() root_identity = state["root_identity"] gate_session = persona_mod.enter_gate_anonymously("journalists", rotate=True)["identity"] gate_persona = persona_mod.create_gate_persona("journalists", label="source-a")["identity"] assert root_identity["node_id"] assert gate_session["node_id"] != root_identity["node_id"] assert gate_session["public_key"] != root_identity["public_key"] assert gate_persona["node_id"] != root_identity["node_id"] assert gate_persona["public_key"] != root_identity["public_key"] def test_gate_persona_activation_is_gate_local(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) first = persona_mod.create_gate_persona("sources", label="source-a") second = persona_mod.create_gate_persona("leaks", label="source-a") assert first["ok"] is True assert second["ok"] is True assert first["identity"]["gate_id"] == "sources" assert second["identity"]["gate_id"] == "leaks" assert first["identity"]["node_id"] != second["identity"]["node_id"] active_sources = persona_mod.get_active_gate_identity("sources") active_leaks = persona_mod.get_active_gate_identity("leaks") assert active_sources["identity"]["persona_id"] == first["identity"]["persona_id"] assert active_leaks["identity"]["persona_id"] == second["identity"]["persona_id"] def test_gate_persona_duplicate_labels_get_unique_suffixes(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) first = persona_mod.create_gate_persona("sources", label="source-a") second = persona_mod.create_gate_persona("sources", label="source-a") third = persona_mod.create_gate_persona("sources", label="Source-A") assert first["ok"] is True assert second["ok"] is True assert third["ok"] is True assert first["identity"]["label"] == "source-a" assert second["identity"]["label"] == "source-a-2" assert third["identity"]["label"] == "Source-A-3" def test_sign_public_event_uses_transport_identity(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) transport_identity = persona_mod.get_transport_identity() signed = persona_mod.sign_public_wormhole_event( event_type="message", payload={ "message": "hello", "destination": "broadcast", "channel": "LongFast", "priority": "normal", "ephemeral": False, }, ) assert signed["identity_scope"] == "transport" assert signed["node_id"] == transport_identity["node_id"] assert signed["public_key"] == transport_identity["public_key"] def test_sign_root_event_uses_root_identity(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) root_identity = persona_mod.get_root_identity() signed = persona_mod.sign_root_wormhole_event( event_type="dm_prekey_root_attestation", payload={"agent_id": "alias-1", "bundle_signature": "sig"}, ) assert signed["identity_scope"] == "root" assert signed["node_id"] == root_identity["node_id"] assert signed["public_key"] == root_identity["public_key"] def test_sign_gate_event_uses_gate_session_identity(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) transport_identity = persona_mod.get_transport_identity() signed = persona_mod.sign_gate_wormhole_event( gate_id="journalists", event_type="gate_message", payload={ "gate": "journalists", "epoch": 1, "ciphertext": "opaque-source-drop", "nonce": "nonce-j1", "sender_ref": "gate-session-j1", }, ) gate_identity = persona_mod.get_active_gate_identity("journalists") assert signed["identity_scope"] == "gate_session" assert signed["gate_id"] == "journalists" assert signed["node_id"] == gate_identity["identity"]["node_id"] assert signed["node_id"] != transport_identity["node_id"] def test_leave_gate_forces_new_anonymous_session_on_reentry(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) first = persona_mod.enter_gate_anonymously("sources", rotate=True) persona_mod.leave_gate("sources") second = persona_mod.enter_gate_anonymously("sources", rotate=False) assert first["identity"]["node_id"] assert second["identity"]["node_id"] assert first["identity"]["node_id"] != second["identity"]["node_id"] def test_gate_session_rotation_uses_jitter_window_before_auto_swap(tmp_path, monkeypatch): from services.config import get_settings persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) monkeypatch.setenv("MESH_GATE_SESSION_ROTATE_MSGS", "1") monkeypatch.setenv("MESH_GATE_SESSION_ROTATE_JITTER_S", "120") get_settings.cache_clear() try: now = {"value": 1_000.0} monkeypatch.setattr(persona_mod.time, "time", lambda: now["value"]) monkeypatch.setattr(persona_mod.random, "uniform", lambda *_args, **_kwargs: 45.0) persona_mod.bootstrap_wormhole_persona_state(force=True) first = persona_mod.enter_gate_anonymously("sources", rotate=True) persona_mod.sign_gate_wormhole_event( gate_id="sources", event_type="gate_message", payload={ "gate": "sources", "epoch": 1, "ciphertext": "opaque", "nonce": "nonce-1", "sender_ref": "sender-ref-1", "format": "mls1", }, ) same = persona_mod.enter_gate_anonymously("sources", rotate=False) scheduled = persona_mod.read_wormhole_persona_state()["gate_sessions"]["sources"]["_rotate_after"] assert same["identity"]["node_id"] == first["identity"]["node_id"] assert scheduled == 1_045.0 now["value"] = 1_046.0 rotated = persona_mod.enter_gate_anonymously("sources", rotate=False) assert rotated["identity"]["node_id"] != first["identity"]["node_id"] finally: get_settings.cache_clear() def test_gate_enter_leave_do_not_emit_public_breadcrumbs(tmp_path, monkeypatch): import main from services.mesh import mesh_hashchain persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) append_called = {"count": 0} def fake_append(**kwargs): append_called["count"] += 1 return {"event_id": "unexpected"} monkeypatch.setattr(mesh_hashchain.infonet, "append", fake_append) body = main.WormholeGateRequest(gate_id="sources", rotate=True) entered = asyncio.run(main.api_wormhole_gate_enter(_request("/api/wormhole/gate/enter"), body)) left = asyncio.run( main.api_wormhole_gate_leave( _request("/api/wormhole/gate/leave"), main.WormholeGateRequest(gate_id="sources"), ) ) assert entered["ok"] is True assert left["ok"] is True assert append_called["count"] == 0 def test_gate_enter_route_allows_private_control_only(tmp_path, monkeypatch): import auth import main from httpx import ASGITransport, AsyncClient from services import wormhole_supervisor _fresh_persona_state(tmp_path, monkeypatch) monkeypatch.setattr(auth, "_debug_mode_enabled", lambda: True) monkeypatch.setattr( wormhole_supervisor, "get_wormhole_state", lambda: {"configured": True, "ready": True, "arti_ready": False, "rns_ready": False}, ) async def _run(): async with AsyncClient(transport=ASGITransport(app=main.app), base_url="http://test") as ac: response = await ac.post("/api/wormhole/gate/enter", json={"gate_id": "sources", "rotate": True}) return response.status_code, response.json() status_code, payload = asyncio.run(_run()) assert status_code == 200 assert payload["ok"] is True assert payload["identity"]["scope"] == "gate_session" def test_clear_active_persona_reverts_gate_to_anonymous_session(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) persona_mod.enter_gate_anonymously("evidence", rotate=True) created = persona_mod.create_gate_persona("evidence", label="reporter") cleared = persona_mod.clear_active_gate_persona("evidence") active = persona_mod.get_active_gate_identity("evidence") assert created["identity"]["scope"] == "gate_persona" assert cleared["ok"] is True assert cleared["identity"]["scope"] == "gate_session" assert active["source"] == "anonymous" def test_sign_gate_event_uses_active_persona_when_selected(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) persona_mod.enter_gate_anonymously("ops", rotate=True) created = persona_mod.create_gate_persona("ops", label="scribe") signed = persona_mod.sign_gate_wormhole_event( gate_id="ops", event_type="gate_message", payload={ "gate": "ops", "epoch": 1, "ciphertext": "opaque-persona-post", "nonce": "nonce-o1", "sender_ref": "persona-ops-1", }, ) assert created["identity"]["persona_id"] assert signed["identity_scope"] == "gate_persona" assert signed["node_id"] == created["identity"]["node_id"] def test_enter_gate_anonymously_clears_existing_active_persona(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) created = persona_mod.create_gate_persona("ops", label="scribe") entered = persona_mod.enter_gate_anonymously("ops", rotate=True) active = persona_mod.get_active_gate_identity("ops") assert created["identity"]["scope"] == "gate_persona" assert entered["identity"]["scope"] == "gate_session" assert active["source"] == "anonymous" assert active["identity"]["node_id"] == entered["identity"]["node_id"] assert active["identity"]["node_id"] != created["identity"]["node_id"] def test_sign_gate_event_rejects_cross_gate_payload_mismatch(tmp_path, monkeypatch): persona_mod, _identity_mod = _fresh_persona_state(tmp_path, monkeypatch) persona_mod.bootstrap_wormhole_persona_state(force=True) persona_mod.enter_gate_anonymously("ops", rotate=True) signed = persona_mod.sign_gate_wormhole_event( gate_id="ops", event_type="gate_message", payload={ "gate": "finance", "epoch": 1, "ciphertext": "opaque-cross-gate-post", "nonce": "nonce-cross-1", "sender_ref": "persona-finance-1", }, ) assert signed["ok"] is False assert signed["detail"] == "gate payload mismatch"