import { beforeEach, describe, expect, it, vi } from 'vitest'; const controlPlaneJson = vi.fn(); const controlPlaneFetch = vi.fn(); const getNodeIdentity = vi.fn< () => { nodeId: string; publicKey: string; privateKey: string } | null >(() => null); const signEvent = vi.fn(); const signMessage = vi.fn(); const signWithStoredKey = vi.fn(); const isSecureModeCached = vi.fn(() => true); const fetchWormholeSettings = vi.fn(async () => ({ enabled: true })); const fetchWormholeState = vi.fn(async () => ({ ready: true })); const connectWormhole = vi.fn(async () => ({ ready: true })); const joinWormhole = vi.fn(async () => ({ ok: true, runtime: { ready: true } })); const hasLocalControlBridge = vi.fn(() => false); const composeBrowserGateMessage = vi.fn(async () => null); const postBrowserGateMessage = vi.fn(async () => null); const decryptBrowserGateMessages = vi.fn(async () => null); const forgetBrowserGateState = vi.fn(async () => {}); const syncBrowserGateState = vi.fn(async () => true); const getBrowserGateCryptoFailureReason = vi.fn(() => 'browser_gate_worker_unavailable'); const getWormholeIdentityDescriptor = vi.fn(() => null); const getGateSessionStreamStatus = vi.fn(() => ({ enabled: false, phase: 'idle', transport: 'sse', sessionId: '', subscriptions: [], heartbeatS: 0, batchMs: 0, lastEventType: '', lastEventAt: 0, detail: '', })); const getGateSessionStreamAccessHeaders = vi.fn(() => undefined); const getGateSessionStreamKeyStatus = vi.fn(() => null); const invalidateGateSessionStreamGateContext = vi.fn(); const setGateSessionStreamGateContext = vi.fn(); vi.mock('@/lib/controlPlane', () => ({ controlPlaneFetch, controlPlaneJson, })); vi.mock('@/lib/localControlTransport', () => ({ hasLocalControlBridge, })); vi.mock('@/mesh/meshGateWorkerClient', () => ({ composeBrowserGateMessage, postBrowserGateMessage, decryptBrowserGateMessages, forgetBrowserGateState, syncBrowserGateState, getBrowserGateCryptoFailureReason, })); vi.mock('@/mesh/gateSessionStream', () => ({ getGateSessionStreamAccessHeaders, getGateSessionStreamStatus, getGateSessionStreamKeyStatus, invalidateGateSessionStreamGateContext, setGateSessionStreamGateContext, })); vi.mock('@/mesh/meshIdentity', () => ({ cacheWormholeIdentityDescriptor: vi.fn(), getNodeIdentity, getPublicKeyAlgo: vi.fn(() => 'ed25519'), getWormholeIdentityDescriptor, isSecureModeCached, purgeBrowserSigningMaterial: vi.fn(async () => {}), setSecureModeCached: vi.fn(), signEvent, signMessage, signWithStoredKey, })); vi.mock('@/mesh/meshProtocol', () => ({ PROTOCOL_VERSION: 'sb-test', })); vi.mock('@/mesh/wormholeClient', () => ({ connectWormhole, fetchWormholeSettings, fetchWormholeState, joinWormhole, })); describe('wormholeIdentityClient strict profile hints', () => { beforeEach(() => { vi.resetModules(); window.localStorage.clear(); window.sessionStorage.clear(); controlPlaneJson.mockReset(); controlPlaneJson.mockResolvedValue({ ok: true }); controlPlaneFetch.mockReset(); controlPlaneFetch.mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ ok: true }), }); getNodeIdentity.mockReset(); getNodeIdentity.mockReturnValue(null); signEvent.mockReset(); signMessage.mockReset(); signWithStoredKey.mockReset(); isSecureModeCached.mockReset(); isSecureModeCached.mockReturnValue(true); fetchWormholeSettings.mockReset(); fetchWormholeSettings.mockResolvedValue({ enabled: true }); fetchWormholeState.mockReset(); fetchWormholeState.mockResolvedValue({ ready: true }); connectWormhole.mockReset(); connectWormhole.mockResolvedValue({ ready: true }); joinWormhole.mockReset(); joinWormhole.mockResolvedValue({ ok: true, runtime: { ready: true } }); hasLocalControlBridge.mockReset(); hasLocalControlBridge.mockReturnValue(false); composeBrowserGateMessage.mockReset(); composeBrowserGateMessage.mockResolvedValue(null); postBrowserGateMessage.mockReset(); postBrowserGateMessage.mockResolvedValue(null); decryptBrowserGateMessages.mockReset(); decryptBrowserGateMessages.mockResolvedValue(null); forgetBrowserGateState.mockReset(); forgetBrowserGateState.mockResolvedValue(undefined); syncBrowserGateState.mockReset(); syncBrowserGateState.mockResolvedValue(true); getBrowserGateCryptoFailureReason.mockReset(); getBrowserGateCryptoFailureReason.mockReturnValue('browser_gate_worker_unavailable'); getWormholeIdentityDescriptor.mockReset(); getWormholeIdentityDescriptor.mockReturnValue(null); getGateSessionStreamStatus.mockReset(); getGateSessionStreamStatus.mockReturnValue({ enabled: false, phase: 'idle', transport: 'sse', sessionId: '', subscriptions: [], heartbeatS: 0, batchMs: 0, lastEventType: '', lastEventAt: 0, detail: '', }); getGateSessionStreamAccessHeaders.mockReset(); getGateSessionStreamAccessHeaders.mockReturnValue(undefined); getGateSessionStreamKeyStatus.mockReset(); getGateSessionStreamKeyStatus.mockReturnValue(null); invalidateGateSessionStreamGateContext.mockReset(); setGateSessionStreamGateContext.mockReset(); }); it('applies strict gate_operator enforcement to gate persona and compose operations', async () => { hasLocalControlBridge.mockReturnValue(true); fetchWormholeState.mockResolvedValue({ ready: true, transport_tier: 'private_transitional', transport_active: 'private_transitional', }); const mod = await import('@/mesh/wormholeIdentityClient'); await mod.listWormholeGatePersonas('infonet'); await mod.createWormholeGatePersona('infonet', 'persona-1'); await mod.activateWormholeGatePersona('infonet', 'persona-1'); await mod.clearWormholeGatePersona('infonet'); await mod.retireWormholeGatePersona('infonet', 'persona-1'); await mod.composeWormholeGateMessage('infonet', 'hello'); await mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1'); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/infonet/personas', expect.objectContaining({ capabilityIntent: 'wormhole_gate_persona', sessionProfileHint: 'gate_operator', enforceProfileHint: true, }), ); for (let i = 2; i <= 5; i += 1) { expect(controlPlaneJson).toHaveBeenNthCalledWith( i, expect.any(String), expect.objectContaining({ capabilityIntent: 'wormhole_gate_persona', sessionProfileHint: 'gate_operator', enforceProfileHint: true, }), ); } expect(controlPlaneJson).toHaveBeenNthCalledWith( 6, '/api/wormhole/gate/message/compose', expect.objectContaining({ capabilityIntent: 'wormhole_gate_content', sessionProfileHint: 'gate_operator', enforceProfileHint: true, body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: '', compat_plaintext: false, }), }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 7, '/api/wormhole/gate/message/post', expect.objectContaining({ capabilityIntent: 'wormhole_gate_content', sessionProfileHint: 'gate_operator', enforceProfileHint: true, method: 'POST', body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: false, }), }), ); }); it('switches gate compose/post out of plaintext compat when the native bridge is available', async () => { hasLocalControlBridge.mockReturnValue(true); fetchWormholeState.mockResolvedValue({ ready: true, transport_tier: 'private_transitional', transport_active: 'private_transitional', }); const mod = await import('@/mesh/wormholeIdentityClient'); await mod.composeWormholeGateMessage('infonet', 'hello'); await mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1'); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/message/compose', expect.objectContaining({ body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: '', compat_plaintext: false, }), }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/message/post', expect.objectContaining({ body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: false, }), }), ); }); it('uses browser-local gate compose but commits posts through the local backend gate sealer', async () => { composeBrowserGateMessage.mockResolvedValue({ ok: true, gate_id: 'infonet', sender_id: '!sb_gate', public_key: 'pub', public_key_algo: 'ed25519', protocol_version: 'sb-test', sequence: 3, signature: 'sig', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', format: 'mls1', }); postBrowserGateMessage.mockResolvedValue({ ok: true }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.composeWormholeGateMessage('infonet', 'hello')).resolves.toEqual( expect.objectContaining({ ok: true, gate_id: 'infonet', format: 'mls1', }), ); await expect(mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1')).resolves.toEqual({ ok: true, }); expect(composeBrowserGateMessage).toHaveBeenCalledWith('infonet', 'hello', ''); expect(postBrowserGateMessage).not.toHaveBeenCalled(); expect(controlPlaneJson).not.toHaveBeenCalledWith( '/api/wormhole/gate/message/compose', expect.anything(), ); expect(controlPlaneJson).toHaveBeenCalledWith( '/api/wormhole/gate/message/post', expect.objectContaining({ capabilityIntent: 'wormhole_gate_content', sessionProfileHint: 'gate_operator', enforceProfileHint: true, method: 'POST', body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: true, }), }), ); }); it('falls back to backend gate compose/post when browser signing cannot carry a durable envelope', async () => { composeBrowserGateMessage.mockResolvedValue({ ok: false, detail: 'gate_envelope_required', }); postBrowserGateMessage.mockResolvedValue({ ok: false, detail: 'gate_envelope_required', }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.composeWormholeGateMessage('infonet', 'hello', 'evt-parent-1')).resolves.toEqual({ ok: true, }); await expect(mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1')).resolves.toEqual({ ok: true, }); expect(composeBrowserGateMessage).toHaveBeenCalledWith('infonet', 'hello', 'evt-parent-1'); expect(postBrowserGateMessage).not.toHaveBeenCalled(); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/message/compose', expect.objectContaining({ capabilityIntent: 'wormhole_gate_content', sessionProfileHint: 'gate_operator', enforceProfileHint: true, method: 'POST', body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: true, }), }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/message/post', expect.objectContaining({ capabilityIntent: 'wormhole_gate_content', sessionProfileHint: 'gate_operator', enforceProfileHint: true, method: 'POST', body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: true, }), }), ); }); it('prefers browser-local gate decrypt over backend compat decrypt when the worker path is available', async () => { decryptBrowserGateMessages.mockResolvedValue({ ok: true, results: [ { ok: true, gate_id: 'infonet', epoch: 7, plaintext: 'sealed', reply_to: 'evt-parent-1', identity_scope: 'browser_privacy_core', }, ], }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect( mod.decryptWormholeGateMessages([ { gate_id: 'infonet', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', format: 'mls1', }, ]), ).resolves.toEqual({ ok: true, results: [ { ok: true, gate_id: 'infonet', epoch: 7, plaintext: 'sealed', reply_to: 'evt-parent-1', identity_scope: 'browser_privacy_core', }, ], }); expect(decryptBrowserGateMessages).toHaveBeenCalledWith([ { gate_id: 'infonet', epoch: 7, ciphertext: 'ct', }, ]); expect(controlPlaneJson).not.toHaveBeenCalledWith( '/api/wormhole/gate/messages/decrypt', expect.anything(), ); }); it('recovers historical gate messages through recovery envelopes after browser-local decrypt fails', async () => { decryptBrowserGateMessages.mockResolvedValue({ ok: true, results: [ { ok: false, gate_id: 'infonet', epoch: 7, detail: 'gate_mls_decrypt_failed', }, ], }); controlPlaneJson.mockResolvedValueOnce({ ok: true, results: [ { ok: true, gate_id: 'infonet', epoch: 7, plaintext: 'history survives re-entry', identity_scope: 'gate_envelope', }, ], }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect( mod.decryptWormholeGateMessages([ { gate_id: 'infonet', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', format: 'mls1', gate_envelope: 'envelope-token', envelope_hash: 'hash-1', }, ]), ).resolves.toEqual({ ok: true, results: [ expect.objectContaining({ ok: true, gate_id: 'infonet', plaintext: 'history survives re-entry', identity_scope: 'gate_envelope', }), ], }); expect(controlPlaneJson).toHaveBeenCalledWith( '/api/wormhole/gate/messages/decrypt', expect.objectContaining({ body: JSON.stringify({ messages: [ { gate_id: 'infonet', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', format: 'mls1', gate_envelope: 'envelope-token', envelope_hash: 'hash-1', recovery_envelope: true, compat_decrypt: false, }, ], }), }), ); }); it('fails closed when browser-local gate runtime is unavailable', async () => { const dispatchSpy = vi.spyOn(window, 'dispatchEvent'); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.composeWormholeGateMessage('infonet', 'hello')).rejects.toThrow( 'gate_local_runtime_required:browser_gate_worker_unavailable', ); await expect(mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1')).resolves.toEqual({ ok: true, }); await expect( mod.decryptWormholeGateMessages([ { gate_id: 'infonet', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', format: 'mls1', }, ]), ).rejects.toThrow('gate_local_runtime_required:browser_gate_worker_unavailable'); const gateFallbackEvents = dispatchSpy.mock.calls .map(([event]) => event) .filter( (event): event is CustomEvent => event instanceof CustomEvent && (event.type === 'sb:gate-compat-consent-required' || event.type === 'sb:gate-compat-fallback'), ); expect(gateFallbackEvents).toEqual([]); expect(controlPlaneJson).not.toHaveBeenCalledWith( '/api/wormhole/gate/message/compose', expect.anything(), ); expect(controlPlaneJson).toHaveBeenCalledWith( '/api/wormhole/gate/message/post', expect.objectContaining({ body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: true, }), }), ); expect(controlPlaneJson).not.toHaveBeenCalledWith( '/api/wormhole/gate/messages/decrypt', expect.anything(), ); }); it('does not let stale compat approval unlock ordinary backend gate compose or decrypt', async () => { const dispatchSpy = vi.spyOn(window, 'dispatchEvent'); const mod = await import('@/mesh/wormholeIdentityClient'); mod.approveGateCompatFallback('infonet'); await expect(mod.composeWormholeGateMessage('infonet', 'hello')).rejects.toThrow( 'gate_local_runtime_required:browser_gate_worker_unavailable', ); await expect(mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1')).resolves.toEqual({ ok: true, }); await expect( mod.decryptWormholeGateMessages([ { gate_id: 'infonet', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', format: 'mls1', }, ]), ).rejects.toThrow('gate_local_runtime_required:browser_gate_worker_unavailable'); const gateFallbackEvents = dispatchSpy.mock.calls .map(([event]) => event) .filter( (event): event is CustomEvent => event instanceof CustomEvent && (event.type === 'sb:gate-compat-consent-required' || event.type === 'sb:gate-compat-fallback'), ); expect(gateFallbackEvents).toEqual([]); expect(controlPlaneJson).not.toHaveBeenCalledWith( '/api/wormhole/gate/message/compose', expect.anything(), ); expect(controlPlaneJson).toHaveBeenCalledWith( '/api/wormhole/gate/message/post', expect.objectContaining({ body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: true, }), }), ); expect(controlPlaneJson).not.toHaveBeenCalledWith( '/api/wormhole/gate/messages/decrypt', expect.anything(), ); }); it('persists gate compat approval across reloads for the current browser profile', async () => { const mod = await import('@/mesh/wormholeIdentityClient'); mod.approveGateCompatFallback('infonet'); expect(mod.hasGateCompatFallbackApproval('infonet')).toBe(true); vi.resetModules(); const reloaded = await import('@/mesh/wormholeIdentityClient'); expect(reloaded.hasGateCompatFallbackApproval('infonet')).toBe(true); }); it('keeps explicit recovery decrypt available when browser-local gate runtime is unavailable', async () => { controlPlaneJson.mockResolvedValue({ ok: true, gate_id: 'infonet', epoch: 7, plaintext: 'recovered', }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect( mod.decryptWormholeGateMessage( 'infonet', 7, 'ct', 'nonce', 'sender-ref', 'envelope-token', 'hash-1', true, ), ).resolves.toEqual( expect.objectContaining({ ok: true, plaintext: 'recovered', }), ); expect(controlPlaneJson).toHaveBeenCalledWith( '/api/wormhole/gate/message/decrypt', expect.objectContaining({ body: JSON.stringify({ gate_id: 'infonet', epoch: 7, ciphertext: 'ct', nonce: 'nonce', sender_ref: 'sender-ref', gate_envelope: 'envelope-token', envelope_hash: 'hash-1', recovery_envelope: true, compat_decrypt: false, }), }), ); }); it('refreshes browser gate state after gate persona mutations and forgets it on leave', async () => { const mod = await import('@/mesh/wormholeIdentityClient'); controlPlaneJson.mockResolvedValue({ ok: true, identity: { node_id: '!sb_gate' } }); await mod.enterWormholeGate('infonet'); await mod.activateWormholeGatePersona('infonet', 'persona-1'); await mod.rotateWormholeGateKey('infonet'); await mod.leaveWormholeGate('infonet'); expect(forgetBrowserGateState).toHaveBeenNthCalledWith(1, 'infonet'); expect(forgetBrowserGateState).toHaveBeenNthCalledWith(2, 'infonet'); expect(forgetBrowserGateState).toHaveBeenNthCalledWith(3, 'infonet'); expect(forgetBrowserGateState).toHaveBeenNthCalledWith(4, 'infonet'); expect(syncBrowserGateState).toHaveBeenNthCalledWith(1, 'infonet', { force: true }); expect(syncBrowserGateState).toHaveBeenNthCalledWith(2, 'infonet', { force: true }); expect(syncBrowserGateState).toHaveBeenNthCalledWith(3, 'infonet', { force: true }); expect(syncBrowserGateState).toHaveBeenCalledTimes(3); expect(invalidateGateSessionStreamGateContext).toHaveBeenCalledWith('infonet'); }); it('re-primes session-stream gate bootstrap context after streamed gate mutations', async () => { getGateSessionStreamStatus.mockReturnValue({ enabled: true, phase: 'open', transport: 'sse', sessionId: 'sess-1', subscriptions: ['infonet'], heartbeatS: 20, batchMs: 1500, lastEventType: 'hello', lastEventAt: 1712345678000, detail: '', }); controlPlaneJson .mockResolvedValueOnce({ ok: true, identity: { node_id: '!sb_gate' }, }) .mockResolvedValueOnce({ node_id: '!sb_stream', ts: 1712345678, proof: 'proof-a', }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 4, has_local_access: true, identity_scope: 'anonymous', }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.enterWormholeGate('infonet')).resolves.toEqual( expect.objectContaining({ ok: true }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/enter', expect.anything(), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/proof', expect.anything(), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 3, '/api/wormhole/gate/infonet/key', expect.anything(), ); expect(setGateSessionStreamGateContext).toHaveBeenCalledWith('infonet', { accessHeaders: { 'X-Wormhole-Node-Id': '!sb_stream', 'X-Wormhole-Gate-Proof': 'proof-a', 'X-Wormhole-Gate-Ts': '1712345678', }, keyStatus: expect.objectContaining({ gate_id: 'infonet', current_epoch: 4, has_local_access: true, }), }); }); it('reuses returned key status when re-priming session-stream gate context after rekey', async () => { getGateSessionStreamStatus.mockReturnValue({ enabled: true, phase: 'open', transport: 'sse', sessionId: 'sess-1', subscriptions: ['infonet'], heartbeatS: 20, batchMs: 1500, lastEventType: 'hello', lastEventAt: 1712345678000, detail: '', }); controlPlaneJson .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 8, has_local_access: true, rotation_reason: 'manual_rotate', }) .mockResolvedValueOnce({ node_id: '!sb_stream', ts: 1712345688, proof: 'proof-b', }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.rotateWormholeGateKey('infonet')).resolves.toEqual( expect.objectContaining({ ok: true, current_epoch: 8 }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/key/rotate', expect.anything(), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/proof', expect.anything(), ); expect(controlPlaneJson).toHaveBeenCalledTimes(2); expect(setGateSessionStreamGateContext).toHaveBeenCalledWith('infonet', { accessHeaders: { 'X-Wormhole-Node-Id': '!sb_stream', 'X-Wormhole-Gate-Proof': 'proof-b', 'X-Wormhole-Gate-Ts': '1712345688', }, keyStatus: expect.objectContaining({ gate_id: 'infonet', current_epoch: 8, has_local_access: true, }), }); }); it('keeps the next session-stream proof and key refresh off the control plane after mutation re-prime', async () => { getGateSessionStreamStatus.mockReturnValue({ enabled: true, phase: 'open', transport: 'sse', sessionId: 'sess-1', subscriptions: ['infonet'], heartbeatS: 20, batchMs: 1500, lastEventType: 'hello', lastEventAt: 1712345678000, detail: '', }); controlPlaneJson .mockResolvedValueOnce({ ok: true, identity: { node_id: '!sb_gate' }, }) .mockResolvedValueOnce({ node_id: '!sb_stream', ts: 1712345678, proof: 'proof-a', }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 4, has_local_access: true, identity_scope: 'anonymous', }); const mod = await import('@/mesh/wormholeIdentityClient'); const accessMod = await import('@/mesh/gateAccessProof'); await expect(mod.enterWormholeGate('infonet')).resolves.toEqual( expect.objectContaining({ ok: true }), ); const latestStreamContext = setGateSessionStreamGateContext.mock.calls.at(-1)?.[1] as | { accessHeaders?: Record; keyStatus?: Record; } | undefined; getGateSessionStreamAccessHeaders.mockReturnValue(latestStreamContext?.accessHeaders); getGateSessionStreamKeyStatus.mockReturnValue(latestStreamContext?.keyStatus ?? null); accessMod.invalidateGateAccessHeaders('infonet'); mod.invalidateWormholeGateKeyStatus('infonet'); controlPlaneJson.mockClear(); await expect( accessMod.buildGateAccessHeaders('infonet', { mode: 'session_stream' }), ).resolves.toEqual({ 'X-Wormhole-Node-Id': '!sb_stream', 'X-Wormhole-Gate-Proof': 'proof-a', 'X-Wormhole-Gate-Ts': '1712345678', }); await expect( mod.fetchWormholeGateKeyStatus('infonet', { mode: 'session_stream' }), ).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 4, has_local_access: true, }), ); expect(controlPlaneJson).not.toHaveBeenCalled(); }); it('auto-connects Wormhole before entering a gate when the lane is configured but not ready yet', async () => { fetchWormholeState .mockResolvedValueOnce({ ready: false }) .mockResolvedValueOnce({ ready: true }); fetchWormholeSettings.mockResolvedValueOnce({ enabled: true }); connectWormhole.mockResolvedValueOnce({ ready: false }); controlPlaneJson .mockResolvedValueOnce({ ok: true, identity: { node_id: '!sb_gate' }, }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 4, has_local_access: true, }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.enterWormholeGate('infonet')).resolves.toEqual( expect.objectContaining({ ok: true }), ); expect(fetchWormholeState).toHaveBeenCalledWith(true); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/enter', expect.anything(), ); }); it('joins Wormhole before entering a gate when the obfuscated lane is not configured yet', async () => { fetchWormholeState.mockResolvedValueOnce({ ready: false, configured: false }); fetchWormholeSettings.mockResolvedValueOnce({ enabled: false }); joinWormhole.mockResolvedValueOnce({ ok: true, runtime: { ready: true }, }); controlPlaneJson .mockResolvedValueOnce({ ok: true, identity: { node_id: '!sb_gate' }, }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 4, has_local_access: true, }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.enterWormholeGate('infonet')).resolves.toEqual( expect.objectContaining({ ok: true }), ); }); it('coarsens browser gate key status fetches through a short cache window', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-04-05T22:00:00.000Z')); try { controlPlaneJson.mockResolvedValue({ ok: true, gate_id: 'infonet', current_epoch: 7, has_local_access: true, }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 7, }), ); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 7, }), ); expect(controlPlaneJson).toHaveBeenCalledTimes(1); vi.advanceTimersByTime(12_001); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 7, }), ); expect(controlPlaneJson).toHaveBeenCalledTimes(2); } finally { vi.useRealTimers(); } }); it('coalesces concurrent gate key status reads for the same gate', async () => { let release: | ((value: { ok: true; gate_id: string; current_epoch: number; has_local_access: boolean; }) => void) | null = null; controlPlaneJson.mockImplementationOnce( () => new Promise((resolve) => { release = resolve as typeof release; }), ); const mod = await import('@/mesh/wormholeIdentityClient'); const first = mod.fetchWormholeGateKeyStatus('infonet'); const second = mod.fetchWormholeGateKeyStatus('infonet', { mode: 'active_room' }); expect(controlPlaneJson).toHaveBeenCalledTimes(1); release?.({ ok: true, gate_id: 'infonet', current_epoch: 7, has_local_access: true, }); await expect(first).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 7, }), ); await expect(second).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 7, }), ); }); it('reuses active-room gate key status slightly longer than ordinary reads when local access is ready', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-04-05T22:00:00.000Z')); try { controlPlaneJson .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 7, has_local_access: true, }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 8, has_local_access: true, }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ current_epoch: 7, has_local_access: true, }), ); vi.advanceTimersByTime(18_000); await expect(mod.fetchWormholeGateKeyStatus('infonet', { mode: 'active_room' })).resolves.toEqual( expect.objectContaining({ current_epoch: 7, has_local_access: true, }), ); expect(controlPlaneJson).toHaveBeenCalledTimes(1); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ current_epoch: 8, has_local_access: true, }), ); expect(controlPlaneJson).toHaveBeenCalledTimes(2); } finally { vi.useRealTimers(); } }); it('reuses session-stream gate key status longer than active-room reads when local access is ready', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-04-05T22:00:00.000Z')); try { controlPlaneJson .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 7, has_local_access: true, }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 8, has_local_access: true, }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ current_epoch: 7, has_local_access: true, }), ); vi.advanceTimersByTime(30_000); await expect(mod.fetchWormholeGateKeyStatus('infonet', { mode: 'session_stream' })).resolves.toEqual( expect.objectContaining({ current_epoch: 7, has_local_access: true, }), ); expect(controlPlaneJson).toHaveBeenCalledTimes(1); await expect(mod.fetchWormholeGateKeyStatus('infonet', { mode: 'active_room' })).resolves.toEqual( expect.objectContaining({ current_epoch: 8, has_local_access: true, }), ); expect(controlPlaneJson).toHaveBeenCalledTimes(2); } finally { vi.useRealTimers(); } }); it('uses session-stream bootstrap key status before falling back to the control plane', async () => { getGateSessionStreamKeyStatus.mockReturnValue({ ok: true, gate_id: 'infonet', current_epoch: 9, has_local_access: true, identity_scope: 'anonymous', }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.fetchWormholeGateKeyStatus('infonet', { mode: 'session_stream' })).resolves.toEqual( expect.objectContaining({ gate_id: 'infonet', current_epoch: 9, has_local_access: true, }), ); expect(getGateSessionStreamKeyStatus).toHaveBeenCalledWith('infonet'); expect(controlPlaneJson).not.toHaveBeenCalled(); }); it('invalidates cached gate key status after gate mutations', async () => { controlPlaneJson .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 1, has_local_access: false, }) .mockResolvedValueOnce({ ok: true, identity: { node_id: '!sb_gate' }, }) .mockResolvedValueOnce({ ok: true, gate_id: 'infonet', current_epoch: 2, has_local_access: true, }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ current_epoch: 1, has_local_access: false, }), ); await expect(mod.enterWormholeGate('infonet')).resolves.toEqual( expect.objectContaining({ ok: true }), ); await expect(mod.fetchWormholeGateKeyStatus('infonet')).resolves.toEqual( expect.objectContaining({ current_epoch: 2, has_local_access: true, }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/infonet/key', expect.objectContaining({ capabilityIntent: 'wormhole_gate_key', }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/enter', expect.anything(), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 3, '/api/wormhole/gate/infonet/key', expect.objectContaining({ capabilityIntent: 'wormhole_gate_key', }), ); }); it('invalidates cached gate access proof after gate mutations', async () => { controlPlaneJson .mockResolvedValueOnce({ node_id: '!sb_gate', ts: 1712345678, proof: 'proof-a', }) .mockResolvedValueOnce({ ok: true, identity: { node_id: '!sb_gate' }, }) .mockResolvedValueOnce({ node_id: '!sb_gate', ts: 1712345688, proof: 'proof-b', }); const mod = await import('@/mesh/wormholeIdentityClient'); const accessMod = await import('@/mesh/gateAccessProof'); await expect(accessMod.buildGateAccessHeaders('infonet')).resolves.toEqual({ 'X-Wormhole-Node-Id': '!sb_gate', 'X-Wormhole-Gate-Proof': 'proof-a', 'X-Wormhole-Gate-Ts': '1712345678', }); await expect(mod.enterWormholeGate('infonet')).resolves.toEqual( expect.objectContaining({ ok: true }), ); await expect(accessMod.buildGateAccessHeaders('infonet')).resolves.toEqual({ 'X-Wormhole-Node-Id': '!sb_gate', 'X-Wormhole-Gate-Proof': 'proof-b', 'X-Wormhole-Gate-Ts': '1712345688', }); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/proof', expect.anything(), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/enter', expect.anything(), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 3, '/api/wormhole/gate/proof', expect.anything(), ); }); it('browser raw signing fails closed instead of falling back to legacy jwk signing', async () => { fetchWormholeSettings.mockResolvedValue({ enabled: false }); fetchWormholeState.mockResolvedValue({ ready: false }); getNodeIdentity.mockReturnValue({ nodeId: '!sb_browser', publicKey: 'browser-pub', privateKey: '', }); signWithStoredKey.mockRejectedValue(new Error('no key')); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.signRawMeshMessage('payload')).rejects.toThrow( 'browser_signing_key_unavailable', ); expect(signWithStoredKey).toHaveBeenCalledWith('payload'); expect(signMessage).not.toHaveBeenCalled(); }); it('keeps the cached secure boundary when wormhole settings fetch fails', async () => { fetchWormholeSettings.mockRejectedValue(new Error('network down')); isSecureModeCached.mockReturnValue(true); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.isWormholeSecureRequired()).resolves.toBe(true); }); it('exports and imports DM invites through the wormhole control plane endpoints', async () => { const mod = await import('@/mesh/wormholeIdentityClient'); controlPlaneJson.mockResolvedValueOnce({ ok: true, peer_id: '!sb_invite_a', trust_fingerprint: 'abc123', invite: { event_type: 'dm_invite' }, }); controlPlaneFetch.mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ ok: true, peer_id: '!sb_invite_b', trust_fingerprint: 'def456', trust_level: 'invite_pinned', contact: {}, }), }); await expect(mod.exportWormholeDmInvite()).resolves.toEqual( expect.objectContaining({ peer_id: '!sb_invite_a', trust_fingerprint: 'abc123', }), ); await expect(mod.importWormholeDmInvite({ event_type: 'dm_invite' }, 'field contact')).resolves.toEqual( expect.objectContaining({ peer_id: '!sb_invite_b', trust_level: 'invite_pinned', }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/dm/invite', expect.objectContaining({ requireAdminSession: false, }), ); expect(controlPlaneFetch).toHaveBeenNthCalledWith( 1, '/api/wormhole/dm/invite/import', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, requireAdminSession: false, body: JSON.stringify({ invite: { event_type: 'dm_invite' }, alias: 'field contact', }), }), ); }); it('fetches DM root health through the wormhole control plane endpoint', async () => { const mod = await import('@/mesh/wormholeIdentityClient'); controlPlaneJson.mockResolvedValueOnce({ ok: true, state: 'current_external', health_state: 'ok', monitoring: { state: 'ok' }, runbook: { urgency: 'none', next_action: '', actions: [] }, alerts: [], witness: { state: 'current', health_state: 'ok' }, transparency: { state: 'current', health_state: 'ok' }, }); await expect(mod.fetchWormholeDmRootHealth()).resolves.toEqual( expect.objectContaining({ state: 'current_external', monitoring: expect.objectContaining({ state: 'ok' }), }), ); expect(controlPlaneJson).toHaveBeenCalledWith('/api/wormhole/dm/root-health', { requireAdminSession: false, }); }); it('prepares the interactive lane through the configured wormhole runtime and bootstraps identity state', async () => { fetchWormholeState.mockResolvedValueOnce({ ready: false, configured: true }); fetchWormholeSettings.mockResolvedValueOnce({ enabled: true }); connectWormhole.mockResolvedValueOnce({ ready: true, configured: true, transport_tier: 'private_transitional', transport_active: 'private_transitional', }); controlPlaneJson.mockResolvedValueOnce({ node_id: '!sb_wormhole', public_key: 'wormhole-pub', public_key_algo: 'ed25519', }); const mod = await import('@/mesh/wormholeIdentityClient'); const prepared = await mod.prepareWormholeInteractiveLane({ bootstrapIdentity: true }); expect(connectWormhole).toHaveBeenCalledTimes(1); expect(connectWormhole).toHaveBeenCalledWith({ requireAdminSession: false }); expect(joinWormhole).not.toHaveBeenCalled(); expect(prepared).toEqual( expect.objectContaining({ ready: true, settingsEnabled: true, transportTier: 'private_transitional', identity: expect.objectContaining({ node_id: '!sb_wormhole', public_key: 'wormhole-pub', }), }), ); }); it('warms the obfuscated lane to the posting tier and retries gate post once on a transport race', async () => { hasLocalControlBridge.mockReturnValue(true); fetchWormholeState .mockResolvedValueOnce({ ready: false, configured: true }) .mockResolvedValueOnce({ ready: true, configured: true, transport_tier: 'private_transitional', transport_active: 'private_transitional', }); fetchWormholeSettings.mockResolvedValueOnce({ enabled: true }); connectWormhole.mockResolvedValueOnce({ ready: true, configured: true, transport_tier: 'private_transitional', transport_active: 'private_transitional', }); controlPlaneJson .mockRejectedValueOnce(new Error('transport tier insufficient')) .mockResolvedValueOnce({ ok: true }); const mod = await import('@/mesh/wormholeIdentityClient'); await expect(mod.postWormholeGateMessage('infonet', 'hello', 'evt-parent-1')).resolves.toEqual({ ok: true, }); expect(connectWormhole).toHaveBeenCalledTimes(1); expect(controlPlaneJson).toHaveBeenNthCalledWith( 1, '/api/wormhole/gate/message/post', expect.objectContaining({ capabilityIntent: 'wormhole_gate_content', sessionProfileHint: 'gate_operator', enforceProfileHint: true, }), ); expect(controlPlaneJson).toHaveBeenNthCalledWith( 2, '/api/wormhole/gate/message/post', expect.objectContaining({ body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello', reply_to: 'evt-parent-1', compat_plaintext: false, }), }), ); }); });