@7h3/protocol 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1317
- package/agentAdapter.d.ts +26 -0
- package/auditLog.d.ts +54 -0
- package/capability.d.ts +67 -0
- package/cborCodec.d.ts +34 -0
- package/conformanceVectors.d.ts +20 -0
- package/encryption.d.ts +85 -0
- package/envelopeCbor.d.ts +33 -0
- package/frameworkAdapters.d.ts +72 -0
- package/gateway.d.ts +53 -0
- package/grpcBinding.d.ts +29 -0
- package/httpBinding.d.ts +50 -0
- package/index.d.ts +39 -0
- package/index.js +3662 -0
- package/keyInfra.d.ts +64 -0
- package/keyRegistry.d.ts +9 -0
- package/keyRotation.d.ts +20 -0
- package/mcpGateway.d.ts +37 -0
- package/mcpTransports.d.ts +62 -0
- package/mcpWrapper.d.ts +83 -0
- package/otel.d.ts +65 -0
- package/package.json +13 -121
- package/policyEnforcer.d.ts +50 -0
- package/policyTelemetryFeedback.d.ts +11 -0
- package/{src/protocol.d.ts → protocol.d.ts} +1 -2
- package/protocolAgent.d.ts +58 -0
- package/protocolBinary.d.ts +8 -0
- package/protocolCapabilities.d.ts +24 -0
- package/protocolReplay.d.ts +35 -0
- package/protocolTransport.d.ts +73 -0
- package/queueBinding.d.ts +43 -0
- package/rateLimiter.d.ts +18 -0
- package/{src/redisClient.ts → redisClient.d.ts} +25 -75
- package/replayStores.d.ts +98 -0
- package/revocation.d.ts +71 -0
- package/routePolicy.d.ts +30 -0
- package/runtimePolicy.d.ts +24 -0
- package/runtimePolicyManager.d.ts +15 -0
- package/runtimePolicyPresets.d.ts +11 -0
- package/signedResponse.d.ts +21 -0
- package/stream.d.ts +59 -0
- package/telemetry.d.ts +79 -0
- package/webhookBinding.d.ts +30 -0
- package/wsBinding.d.ts +52 -0
- package/.dockerignore +0 -19
- package/.github/dependabot.yml +0 -32
- package/.github/workflows/ci.yml +0 -31
- package/.github/workflows/publish.yml +0 -59
- package/.github/workflows/scorecard.yml +0 -37
- package/7h3.example.yaml +0 -125
- package/CHANGELOG.md +0 -152
- package/CONTRIBUTING.md +0 -82
- package/Dockerfile +0 -73
- package/GOVERNANCE.md +0 -62
- package/SECURITY.md +0 -70
- package/bench-results/replay-cache-full-1777891033256.json +0 -10
- package/bench-results/replay-cache-full-1777896317488.json +0 -10
- package/bench-results/replay-cache-full-1777900993184.json +0 -10
- package/bench-results/replay-cache-full-1777901019285.json +0 -10
- package/bench-results/replay-cache-quick-1777870170126.json +0 -10
- package/bench-results/signature-profiles-quick-1775875160079.json +0 -85
- package/bench-results/signature-profiles-quick-1775983539716.json +0 -85
- package/bench-results/signature-profiles-quick-1776237913190.json +0 -85
- package/bench-results/wire-codecs-full-1777891019803.json +0 -93
- package/bench-results/wire-codecs-full-1777896260964.json +0 -93
- package/bench-results/wire-codecs-full-1777901004247.json +0 -93
- package/bench-results/wire-codecs-quick-1775972879056.json +0 -93
- package/bench-results/wire-codecs-quick-1775983541111.json +0 -93
- package/bench-results/wire-codecs-quick-1776237914299.json +0 -93
- package/bench-results/wire-codecs-quick-1777841285236.json +0 -93
- package/bench-results/wire-codecs-quick-1777841321772.json +0 -93
- package/bench-results/wire-codecs-quick-1777841330408.json +0 -93
- package/bench-results/wire-codecs-quick-1777852886082.json +0 -93
- package/bench-results/wire-codecs-quick-1777852988773.json +0 -93
- package/bench-results/wire-codecs-quick-1777870188095.json +0 -93
- package/bench-results/wire-codecs-quick-1777870263918.json +0 -93
- package/bench-results/wire-codecs-quick-1777870455034.json +0 -93
- package/bench-results/wire-codecs-quick-1778816163081.json +0 -93
- package/bench-results/wire-codecs-quick-1778843936130.json +0 -93
- package/bin/7h3.ts +0 -406
- package/conformance/7h3_v0_1.json +0 -77
- package/conformance/7h3_v0_1_binary.json +0 -20
- package/conformance/aip_v0_1_binary.json +0 -20
- package/docker-compose.yaml +0 -77
- package/docs/ADOPTION_PLAN.md +0 -120
- package/docs/AGENTS.md +0 -77
- package/docs/AIP_RFC_v0.1.md +0 -97
- package/docs/AI_DECISION_CARD.md +0 -122
- package/docs/AI_RUNTIME_POLICY.json +0 -126
- package/docs/AI_RUNTIME_POLICY.yaml +0 -110
- package/docs/BACKPRESSURE_TUNING.md +0 -65
- package/docs/BENCHMARK_CLAIM_MATRIX.md +0 -42
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +0 -169
- package/docs/BINARY_CODEC_BENCH.md +0 -23
- package/docs/CLEAN_CLONE_RUNBOOK.md +0 -36
- package/docs/CLOCK_SKEW_POLICY.md +0 -30
- package/docs/DISTRIBUTED_REPLAY.md +0 -142
- package/docs/FUZZ_CAMPAIGN.md +0 -121
- package/docs/GATEWAY.md +0 -195
- package/docs/KEY_MANAGEMENT_POLICY.md +0 -53
- package/docs/KEY_REVOCATION.md +0 -69
- package/docs/MCP_WRAPPER.md +0 -159
- package/docs/MIGRATION_GUIDE.md +0 -40
- package/docs/OPERATORS.md +0 -184
- package/docs/PERF_REGRESSION_POLICY.md +0 -34
- package/docs/PROJECT_EXAMINATION_2026-05-31.md +0 -219
- package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +0 -135
- package/docs/RELEASE_GATE.md +0 -25
- package/docs/RELEASE_NOTES_v0.1.0.md +0 -54
- package/docs/SECURITY_REVIEW_2026-06-05.md +0 -165
- package/docs/TELEMETRY.md +0 -41
- package/docs/THREAT_MODEL.md +0 -89
- package/docs/VERSIONING_POLICY.md +0 -30
- package/docs/assets/banner-github.png +0 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/banner.svg +0 -123
- package/eslint.config.js +0 -15
- package/fuzz/ts/harness-decode.ts +0 -136
- package/fuzz/ts/harness-verify.ts +0 -121
- package/fuzz/ts/run.ts +0 -35
- package/mcp-server/README.md +0 -38
- package/mcp-server/package-lock.json +0 -1187
- package/mcp-server/package.json +0 -35
- package/mcp-server/src/index.ts +0 -236
- package/mcp-server/tsconfig.json +0 -14
- package/scripts/aip-framework-quickstart.ts +0 -110
- package/scripts/aip-mcp-gateway.ts +0 -38
- package/scripts/aip-mcp-wrap-demo.ts +0 -72
- package/scripts/aip-quickstart.ts +0 -60
- package/scripts/bench-diff.ts +0 -118
- package/scripts/bench-protocol-e2e.ts +0 -937
- package/scripts/bench-protocol-openloop.ts +0 -1397
- package/scripts/bench-replay-cache.ts +0 -76
- package/scripts/bench-signature-profiles.ts +0 -180
- package/scripts/bench-wire-codecs.ts +0 -161
- package/scripts/build-binary-conformance.ts +0 -36
- package/scripts/build-release-dashboard.ts +0 -175
- package/scripts/canary-rollout.ts +0 -38
- package/scripts/mcpGatewayCli.test.ts +0 -116
- package/scripts/prepare-aip-package.ts +0 -88
- package/scripts/regen-conformance-sigs.ts +0 -18
- package/scripts/release-gate.ts +0 -19
- package/scripts/validate-runtime-policy.ts +0 -18
- package/sdk/browser/index.test.ts +0 -162
- package/sdk/browser/index.ts +0 -257
- package/sdk/browser/package.json +0 -13
- package/sdk/go/cbor.go +0 -551
- package/sdk/go/cbor_test.go +0 -232
- package/sdk/go/encryption.go +0 -280
- package/sdk/go/encryption_test.go +0 -318
- package/sdk/go/go.mod +0 -7
- package/sdk/go/go.sum +0 -4
- package/sdk/go/http.go +0 -135
- package/sdk/go/protocol.go +0 -324
- package/sdk/go/protocol_test.go +0 -334
- package/sdk/go/replay.go +0 -121
- package/sdk/go/replay_test.go +0 -149
- package/sdk/go/webhook.go +0 -136
- package/sdk/pq/package-lock.json +0 -1358
- package/sdk/pq/package.json +0 -42
- package/sdk/pq/src/index.test.ts +0 -143
- package/sdk/pq/src/index.ts +0 -166
- package/sdk/pq/tsconfig.json +0 -14
- package/sdk/pq/vitest.config.ts +0 -7
- package/sdk/python/README.md +0 -18
- package/sdk/python/protocol_7h3/__init__.py +0 -46
- package/sdk/python/protocol_7h3/encryption.py +0 -252
- package/sdk/python/protocol_7h3/http.py +0 -212
- package/sdk/python/protocol_7h3/keys.py +0 -149
- package/sdk/python/protocol_7h3/pq.py +0 -244
- package/sdk/python/protocol_7h3/protocol.py +0 -525
- package/sdk/python/protocol_7h3/queue.py +0 -118
- package/sdk/python/protocol_7h3/replay.py +0 -98
- package/sdk/python/protocol_7h3/webhook.py +0 -116
- package/sdk/python/pyproject.toml +0 -40
- package/sdk/python/tests/test_conformance.py +0 -110
- package/sdk/python/tests/test_encryption.py +0 -206
- package/sdk/python/tests/test_http.py +0 -305
- package/sdk/python/tests/test_keys.py +0 -417
- package/sdk/python/tests/test_queue.py +0 -120
- package/sdk/python/tests/test_webhook.py +0 -345
- package/sdk/rust/Cargo.lock +0 -371
- package/sdk/rust/Cargo.toml +0 -25
- package/sdk/rust/README.md +0 -31
- package/sdk/rust/fuzz/Cargo.toml +0 -29
- package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
- package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
- package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
- package/sdk/rust/src/http.rs +0 -145
- package/sdk/rust/src/keys.rs +0 -161
- package/sdk/rust/src/lib.rs +0 -688
- package/sdk/rust/src/queue.rs +0 -79
- package/sdk/rust/src/webhook.rs +0 -86
- package/sdk/rust/tests/conformance.rs +0 -148
- package/sdk/rust/tests/gateway.rs +0 -130
- package/sdk/rust/tests/http_webhook_queue.rs +0 -201
- package/sdk/rust/tests/keys.rs +0 -189
- package/sdk/threshold/index.d.ts +0 -68
- package/sdk/threshold/index.d.ts.map +0 -1
- package/sdk/threshold/index.js +0 -254
- package/sdk/threshold/package-lock.json +0 -1361
- package/sdk/threshold/package.json +0 -39
- package/sdk/threshold/src/index.d.ts +0 -68
- package/sdk/threshold/src/index.d.ts.map +0 -1
- package/sdk/threshold/src/index.js +0 -254
- package/sdk/threshold/src/index.test.ts +0 -238
- package/sdk/threshold/src/index.ts +0 -355
- package/sdk/threshold/tsconfig.json +0 -19
- package/sdk/threshold/vitest.config.ts +0 -12
- package/src/agentAdapter.test.ts +0 -48
- package/src/agentAdapter.ts +0 -56
- package/src/auditLog.test.ts +0 -145
- package/src/auditLog.ts +0 -147
- package/src/capability.test.ts +0 -504
- package/src/capability.ts +0 -380
- package/src/cborCodec.test.ts +0 -263
- package/src/cborCodec.ts +0 -339
- package/src/conformance.test.ts +0 -136
- package/src/conformanceVectors.ts +0 -99
- package/src/encryption.test.ts +0 -206
- package/src/encryption.ts +0 -245
- package/src/envelopeCbor.ts +0 -140
- package/src/frameworkAdapters.test.ts +0 -290
- package/src/frameworkAdapters.ts +0 -261
- package/src/gateway.test.ts +0 -343
- package/src/gateway.ts +0 -246
- package/src/grpcBinding.test.ts +0 -211
- package/src/grpcBinding.ts +0 -103
- package/src/httpBinding.test.ts +0 -376
- package/src/httpBinding.ts +0 -189
- package/src/index.ts +0 -39
- package/src/keyInfra.test.ts +0 -278
- package/src/keyInfra.ts +0 -228
- package/src/keyRegistry.ts +0 -59
- package/src/keyRotation.test.ts +0 -78
- package/src/keyRotation.ts +0 -72
- package/src/mcpGateway.test.ts +0 -129
- package/src/mcpGateway.ts +0 -250
- package/src/mcpTransports.test.ts +0 -92
- package/src/mcpTransports.ts +0 -169
- package/src/mcpWrapper.test.ts +0 -179
- package/src/mcpWrapper.ts +0 -206
- package/src/otel.ts +0 -136
- package/src/policyEnforcer.test.ts +0 -99
- package/src/policyEnforcer.ts +0 -169
- package/src/policyTelemetryFeedback.test.ts +0 -25
- package/src/policyTelemetryFeedback.ts +0 -38
- package/src/protocol.bench.ts +0 -37
- package/src/protocol.d.ts.map +0 -1
- package/src/protocol.js +0 -294
- package/src/protocol.test.ts +0 -155
- package/src/protocol.ts +0 -414
- package/src/protocolAgent.test.ts +0 -105
- package/src/protocolAgent.ts +0 -169
- package/src/protocolBinary.test.ts +0 -165
- package/src/protocolBinary.ts +0 -312
- package/src/protocolCapabilities.ts +0 -70
- package/src/protocolFuzz.advanced.test.ts +0 -235
- package/src/protocolFuzz.test.ts +0 -111
- package/src/protocolNegative.test.ts +0 -97
- package/src/protocolReplay.test.ts +0 -71
- package/src/protocolReplay.ts +0 -194
- package/src/protocolTransport.test.ts +0 -556
- package/src/protocolTransport.ts +0 -483
- package/src/queueBinding.test.ts +0 -130
- package/src/queueBinding.ts +0 -102
- package/src/rateLimiter.test.ts +0 -96
- package/src/rateLimiter.ts +0 -46
- package/src/redisIntegration.test.ts +0 -134
- package/src/replayStores.test.ts +0 -273
- package/src/replayStores.ts +0 -215
- package/src/revocation.test.ts +0 -98
- package/src/revocation.ts +0 -0
- package/src/routePolicy.test.ts +0 -87
- package/src/routePolicy.ts +0 -72
- package/src/runtimePolicy.test.ts +0 -49
- package/src/runtimePolicy.ts +0 -81
- package/src/runtimePolicyManager.test.ts +0 -29
- package/src/runtimePolicyManager.ts +0 -50
- package/src/runtimePolicyPresets.ts +0 -43
- package/src/signedResponse.test.ts +0 -111
- package/src/signedResponse.ts +0 -83
- package/src/stream.test.ts +0 -254
- package/src/stream.ts +0 -417
- package/src/telemetry.test.ts +0 -251
- package/src/telemetry.ts +0 -299
- package/src/webhookBinding.test.ts +0 -144
- package/src/webhookBinding.ts +0 -115
- package/src/wsBinding.test.ts +0 -221
- package/src/wsBinding.ts +0 -200
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -23
- package/vite.lib.config.ts +0 -16
- package/vitest.config.ts +0 -11
package/src/wsBinding.test.ts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { wrapWebSocket, generateEd25519KeypairBase64Url, type WebSocketLike } from './wsBinding'
|
|
3
|
-
import { signEnvelopeEd25519, createEnvelope, type ProtocolEnvelope } from './protocol'
|
|
4
|
-
import { createStaticKeyRegistry } from './keyRegistry'
|
|
5
|
-
|
|
6
|
-
// Mock WebSocket class that buffers sent messages and lets you inject received messages
|
|
7
|
-
class MockWebSocket implements WebSocketLike {
|
|
8
|
-
public sent: string[] = []
|
|
9
|
-
private messageHandlers: Array<(e: { data: string }) => void> = []
|
|
10
|
-
private closeHandlers: Array<() => void> = []
|
|
11
|
-
readyState = 1
|
|
12
|
-
|
|
13
|
-
send(data: string): void {
|
|
14
|
-
this.sent.push(data)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
addEventListener(event: string, handler: (...args: any[]) => void): void {
|
|
18
|
-
if (event === 'message') {
|
|
19
|
-
this.messageHandlers.push(handler as (e: { data: string }) => void)
|
|
20
|
-
} else if (event === 'close') {
|
|
21
|
-
this.closeHandlers.push(handler as () => void)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
removeEventListener(event: string, handler: (...args: unknown[]) => void): void {
|
|
26
|
-
if (event === 'message') {
|
|
27
|
-
const idx = this.messageHandlers.indexOf(handler as any)
|
|
28
|
-
if (idx !== -1) this.messageHandlers.splice(idx, 1)
|
|
29
|
-
} else if (event === 'close') {
|
|
30
|
-
const idx = this.closeHandlers.indexOf(handler as any)
|
|
31
|
-
if (idx !== -1) this.closeHandlers.splice(idx, 1)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Inject a received message
|
|
36
|
-
receive(data: string): void {
|
|
37
|
-
for (const h of this.messageHandlers) h({ data })
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
close(): void {
|
|
41
|
-
for (const h of this.closeHandlers) h()
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe('wrapWebSocket', () => {
|
|
46
|
-
it('send() produces a signed JSON frame', async () => {
|
|
47
|
-
const keypair = await generateEd25519KeypairBase64Url()
|
|
48
|
-
const ws = new MockWebSocket()
|
|
49
|
-
const registry = createStaticKeyRegistry({ 'agent-A': keypair.publicKey })
|
|
50
|
-
|
|
51
|
-
const pws = wrapWebSocket(ws, {
|
|
52
|
-
privateKey: keypair.privateKey,
|
|
53
|
-
sender: 'agent-A',
|
|
54
|
-
keyRegistry: registry,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
await pws.send({ hello: 'world' })
|
|
58
|
-
|
|
59
|
-
expect(ws.sent).toHaveLength(1)
|
|
60
|
-
const frame = JSON.parse(ws.sent[0]) as ProtocolEnvelope
|
|
61
|
-
expect(frame.header.sender).toBe('agent-A')
|
|
62
|
-
expect(frame.signature).toBeDefined()
|
|
63
|
-
expect(frame.signature?.alg).toBe('ED25519')
|
|
64
|
-
expect(frame.body.intent).toBe('TASK')
|
|
65
|
-
expect(frame.body.content).toBe(JSON.stringify({ hello: 'world' }))
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('incoming signed frame calls onMessage handler', async () => {
|
|
69
|
-
const keypair = await generateEd25519KeypairBase64Url()
|
|
70
|
-
const ws = new MockWebSocket()
|
|
71
|
-
const registry = createStaticKeyRegistry({ 'agent-B': keypair.publicKey })
|
|
72
|
-
|
|
73
|
-
const pws = wrapWebSocket(ws, {
|
|
74
|
-
privateKey: keypair.privateKey,
|
|
75
|
-
sender: 'agent-A',
|
|
76
|
-
keyRegistry: registry,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const received: Array<{ payload: unknown; envelope: ProtocolEnvelope }> = []
|
|
80
|
-
pws.onMessage((payload, envelope) => received.push({ payload, envelope }))
|
|
81
|
-
|
|
82
|
-
// Create a valid signed frame from agent-B
|
|
83
|
-
const envelope = createEnvelope({
|
|
84
|
-
sender: 'agent-B',
|
|
85
|
-
intent: 'TASK',
|
|
86
|
-
content: JSON.stringify({ ping: true }),
|
|
87
|
-
ttlMs: 30_000,
|
|
88
|
-
})
|
|
89
|
-
const signed = await signEnvelopeEd25519(envelope, keypair.privateKey)
|
|
90
|
-
|
|
91
|
-
// Wait for async message processing
|
|
92
|
-
await new Promise<void>(resolve => {
|
|
93
|
-
pws.onMessage(() => resolve())
|
|
94
|
-
ws.receive(JSON.stringify(signed))
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
expect(received).toHaveLength(1)
|
|
98
|
-
expect(received[0].payload).toEqual({ ping: true })
|
|
99
|
-
expect(received[0].envelope.header.sender).toBe('agent-B')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('tampered frame calls onVerifyFail', async () => {
|
|
103
|
-
const keypair = await generateEd25519KeypairBase64Url()
|
|
104
|
-
const ws = new MockWebSocket()
|
|
105
|
-
const registry = createStaticKeyRegistry({ 'agent-B': keypair.publicKey })
|
|
106
|
-
|
|
107
|
-
const pws = wrapWebSocket(ws, {
|
|
108
|
-
privateKey: keypair.privateKey,
|
|
109
|
-
sender: 'agent-A',
|
|
110
|
-
keyRegistry: registry,
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
const errors: Error[] = []
|
|
114
|
-
pws.onVerifyFail((err) => errors.push(err))
|
|
115
|
-
|
|
116
|
-
// Create a valid signed frame, then tamper with body content
|
|
117
|
-
const envelope = createEnvelope({
|
|
118
|
-
sender: 'agent-B',
|
|
119
|
-
intent: 'TASK',
|
|
120
|
-
content: 'original content',
|
|
121
|
-
ttlMs: 30_000,
|
|
122
|
-
})
|
|
123
|
-
const signed = await signEnvelopeEd25519(envelope, keypair.privateKey)
|
|
124
|
-
// Tamper with content after signing
|
|
125
|
-
const tampered = { ...signed, body: { ...signed.body, content: 'tampered content' } }
|
|
126
|
-
|
|
127
|
-
await new Promise<void>(resolve => {
|
|
128
|
-
pws.onVerifyFail(() => resolve())
|
|
129
|
-
ws.receive(JSON.stringify(tampered))
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
expect(errors).toHaveLength(1)
|
|
133
|
-
expect(errors[0].message).toContain('invalid signature')
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('unknown sender calls onVerifyFail', async () => {
|
|
137
|
-
const keypair = await generateEd25519KeypairBase64Url()
|
|
138
|
-
const ws = new MockWebSocket()
|
|
139
|
-
// Registry has no entry for 'unknown-agent'
|
|
140
|
-
const registry = createStaticKeyRegistry({})
|
|
141
|
-
|
|
142
|
-
const pws = wrapWebSocket(ws, {
|
|
143
|
-
privateKey: keypair.privateKey,
|
|
144
|
-
sender: 'agent-A',
|
|
145
|
-
keyRegistry: registry,
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
const errors: Error[] = []
|
|
149
|
-
pws.onVerifyFail((err) => errors.push(err))
|
|
150
|
-
|
|
151
|
-
const envelope = createEnvelope({
|
|
152
|
-
sender: 'unknown-agent',
|
|
153
|
-
intent: 'TASK',
|
|
154
|
-
content: 'hello',
|
|
155
|
-
ttlMs: 30_000,
|
|
156
|
-
})
|
|
157
|
-
const signed = await signEnvelopeEd25519(envelope, keypair.privateKey)
|
|
158
|
-
|
|
159
|
-
await new Promise<void>(resolve => {
|
|
160
|
-
pws.onVerifyFail(() => resolve())
|
|
161
|
-
ws.receive(JSON.stringify(signed))
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
expect(errors).toHaveLength(1)
|
|
165
|
-
expect(errors[0].message).toContain('unknown sender')
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('malformed JSON calls onVerifyFail', async () => {
|
|
169
|
-
const keypair = await generateEd25519KeypairBase64Url()
|
|
170
|
-
const ws = new MockWebSocket()
|
|
171
|
-
const registry = createStaticKeyRegistry({})
|
|
172
|
-
|
|
173
|
-
const pws = wrapWebSocket(ws, {
|
|
174
|
-
privateKey: keypair.privateKey,
|
|
175
|
-
sender: 'agent-A',
|
|
176
|
-
keyRegistry: registry,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
const errors: Error[] = []
|
|
180
|
-
pws.onVerifyFail((err) => errors.push(err))
|
|
181
|
-
|
|
182
|
-
await new Promise<void>(resolve => {
|
|
183
|
-
pws.onVerifyFail(() => resolve())
|
|
184
|
-
ws.receive('this is not valid JSON {{{')
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
expect(errors).toHaveLength(1)
|
|
188
|
-
expect(errors[0].message).toContain('malformed frame')
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('seq increments on each send', async () => {
|
|
192
|
-
const keypair = await generateEd25519KeypairBase64Url()
|
|
193
|
-
const ws = new MockWebSocket()
|
|
194
|
-
const registry = createStaticKeyRegistry({ 'agent-A': keypair.publicKey })
|
|
195
|
-
|
|
196
|
-
const pws = wrapWebSocket(ws, {
|
|
197
|
-
privateKey: keypair.privateKey,
|
|
198
|
-
sender: 'agent-A',
|
|
199
|
-
keyRegistry: registry,
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
expect(pws.seq).toBe(0)
|
|
203
|
-
|
|
204
|
-
await pws.send('first')
|
|
205
|
-
expect(pws.seq).toBe(1)
|
|
206
|
-
|
|
207
|
-
await pws.send('second')
|
|
208
|
-
expect(pws.seq).toBe(2)
|
|
209
|
-
|
|
210
|
-
await pws.send('third')
|
|
211
|
-
expect(pws.seq).toBe(3)
|
|
212
|
-
|
|
213
|
-
// Verify correlationId carries the seq number
|
|
214
|
-
const frame1 = JSON.parse(ws.sent[0]) as ProtocolEnvelope
|
|
215
|
-
const frame2 = JSON.parse(ws.sent[1]) as ProtocolEnvelope
|
|
216
|
-
const frame3 = JSON.parse(ws.sent[2]) as ProtocolEnvelope
|
|
217
|
-
expect(frame1.body.correlationId).toBe('1')
|
|
218
|
-
expect(frame2.body.correlationId).toBe('2')
|
|
219
|
-
expect(frame3.body.correlationId).toBe('3')
|
|
220
|
-
})
|
|
221
|
-
})
|
package/src/wsBinding.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
signEnvelopeEd25519, verifyEnvelopeEd25519,
|
|
3
|
-
createEnvelope, validateEnvelope,
|
|
4
|
-
generateEd25519KeypairBase64Url, type ProtocolEnvelope
|
|
5
|
-
} from './protocol'
|
|
6
|
-
import type { KeyRegistry } from './keyRegistry'
|
|
7
|
-
import {
|
|
8
|
-
SignedStreamWriter,
|
|
9
|
-
verifyStream,
|
|
10
|
-
decodeStreamChunk,
|
|
11
|
-
encodeStreamChunk,
|
|
12
|
-
type StreamSignerOpts,
|
|
13
|
-
type StreamVerifierOpts,
|
|
14
|
-
type StreamVerifyResult,
|
|
15
|
-
type StreamChunk,
|
|
16
|
-
} from './stream'
|
|
17
|
-
|
|
18
|
-
export { type KeyRegistry, generateEd25519KeypairBase64Url }
|
|
19
|
-
export type { StreamSignerOpts, StreamVerifierOpts, StreamVerifyResult, StreamChunk }
|
|
20
|
-
|
|
21
|
-
// Minimal WebSocket interface (works with browser WebSocket, ws library, etc.)
|
|
22
|
-
export interface WebSocketLike {
|
|
23
|
-
send(data: string): void
|
|
24
|
-
addEventListener(event: 'message', handler: (e: { data: string | Buffer }) => void): void
|
|
25
|
-
addEventListener(event: 'close', handler: () => void): void
|
|
26
|
-
removeEventListener(event: string, handler: (...args: unknown[]) => void): void
|
|
27
|
-
readyState?: number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface WsBindingOptions {
|
|
31
|
-
privateKey: string
|
|
32
|
-
sender: string
|
|
33
|
-
recipient?: string
|
|
34
|
-
keyRegistry: KeyRegistry // to look up peer public keys by sender ID
|
|
35
|
-
ttlMs?: number // per-frame TTL, default 30_000
|
|
36
|
-
onVerifyFail?: (err: Error, rawData: string) => void
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface ProtectedWebSocket {
|
|
40
|
-
// Send a payload — automatically signs it
|
|
41
|
-
send(payload: unknown): Promise<void>
|
|
42
|
-
// Register handler for verified incoming messages
|
|
43
|
-
onMessage(handler: (payload: unknown, envelope: ProtocolEnvelope) => void): void
|
|
44
|
-
// Register handler for verify failures (optional)
|
|
45
|
-
onVerifyFail(handler: (err: Error, rawData: string) => void): void
|
|
46
|
-
// Sequence number of last sent message
|
|
47
|
-
readonly seq: number
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function wrapWebSocket(ws: WebSocketLike, opts: WsBindingOptions): ProtectedWebSocket {
|
|
51
|
-
let seq = 0
|
|
52
|
-
const messageHandlers: Array<(payload: unknown, envelope: ProtocolEnvelope) => void> = []
|
|
53
|
-
const failHandlers: Array<(err: Error, rawData: string) => void> = []
|
|
54
|
-
|
|
55
|
-
function fireVerifyFail(err: Error, raw: string) {
|
|
56
|
-
opts.onVerifyFail?.(err, raw)
|
|
57
|
-
for (const h of failHandlers) h(err, raw)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Listen for incoming frames
|
|
61
|
-
const messageListener = async (e: { data: string | Buffer }) => {
|
|
62
|
-
const raw = typeof e.data === 'string' ? e.data : e.data.toString('utf8')
|
|
63
|
-
let envelope: ProtocolEnvelope
|
|
64
|
-
try { envelope = JSON.parse(raw) as ProtocolEnvelope }
|
|
65
|
-
catch (err) { fireVerifyFail(new Error('malformed frame'), raw); return }
|
|
66
|
-
|
|
67
|
-
const sender = envelope?.header?.sender
|
|
68
|
-
if (!sender) { fireVerifyFail(new Error('missing sender'), raw); return }
|
|
69
|
-
|
|
70
|
-
const publicKey = await opts.keyRegistry.getPublicKey(sender)
|
|
71
|
-
if (!publicKey) { fireVerifyFail(new Error(`unknown sender: ${sender}`), raw); return }
|
|
72
|
-
|
|
73
|
-
const valid = await verifyEnvelopeEd25519(envelope, publicKey).catch(() => false)
|
|
74
|
-
if (!valid) { fireVerifyFail(new Error('invalid signature'), raw); return }
|
|
75
|
-
|
|
76
|
-
const diags = validateEnvelope(envelope)
|
|
77
|
-
if (diags.some(d => d.level === 'error')) {
|
|
78
|
-
fireVerifyFail(new Error(`envelope invalid: ${diags.map(d => d.message).join('; ')}`), raw)
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let payload: unknown = envelope.body.content
|
|
83
|
-
try { payload = JSON.parse(envelope.body.content) } catch { /* keep as string */ }
|
|
84
|
-
for (const h of messageHandlers) h(payload, envelope)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
ws.addEventListener('message', messageListener as any)
|
|
88
|
-
ws.addEventListener('close', () => {
|
|
89
|
-
ws.removeEventListener('message', messageListener as any)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
get seq() { return seq },
|
|
94
|
-
async send(payload: unknown): Promise<void> {
|
|
95
|
-
const content = typeof payload === 'string' ? payload : JSON.stringify(payload)
|
|
96
|
-
const envelope = createEnvelope({
|
|
97
|
-
sender: opts.sender,
|
|
98
|
-
recipient: opts.recipient,
|
|
99
|
-
ttlMs: opts.ttlMs ?? 30_000,
|
|
100
|
-
intent: 'TASK',
|
|
101
|
-
content,
|
|
102
|
-
})
|
|
103
|
-
// embed sequence number in correlationId to help downstream ordering
|
|
104
|
-
;(envelope.body as any).correlationId = String(++seq)
|
|
105
|
-
const signed = await signEnvelopeEd25519(envelope, opts.privateKey)
|
|
106
|
-
ws.send(JSON.stringify(signed))
|
|
107
|
-
},
|
|
108
|
-
onMessage(handler) { messageHandlers.push(handler) },
|
|
109
|
-
onVerifyFail(handler) { failHandlers.push(handler) },
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ─────────────────── Stream signing over WebSocket ────────────────────────────
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* createSignedWebSocketStream
|
|
117
|
-
*
|
|
118
|
-
* Attaches a SignedStreamWriter to a WebSocket:
|
|
119
|
-
* - Each incoming WebSocket message is signed as a stream chunk and sent back.
|
|
120
|
-
* - On connection close, the final signed frame is sent.
|
|
121
|
-
*
|
|
122
|
-
* Returns the underlying SignedStreamWriter so callers can also call writeChunk
|
|
123
|
-
* directly (e.g. when driving the stream from application code rather than
|
|
124
|
-
* forwarding received messages).
|
|
125
|
-
*/
|
|
126
|
-
export function createSignedWebSocketStream(
|
|
127
|
-
ws: WebSocketLike,
|
|
128
|
-
opts: StreamSignerOpts,
|
|
129
|
-
): SignedStreamWriter {
|
|
130
|
-
const writer = new SignedStreamWriter(opts)
|
|
131
|
-
|
|
132
|
-
const messageListener = async (e: { data: string | Buffer }) => {
|
|
133
|
-
const data = typeof e.data === 'string' ? e.data : e.data.toString('utf8')
|
|
134
|
-
const chunk = await writer.writeChunk(data)
|
|
135
|
-
ws.send(encodeStreamChunk(chunk))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const closeListener = async () => {
|
|
139
|
-
ws.removeEventListener('message', messageListener as any)
|
|
140
|
-
ws.removeEventListener('close', closeListener as any)
|
|
141
|
-
const finalChunk = await writer.finalize()
|
|
142
|
-
ws.send(encodeStreamChunk(finalChunk))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
ws.addEventListener('message', messageListener as any)
|
|
146
|
-
ws.addEventListener('close', closeListener as any)
|
|
147
|
-
|
|
148
|
-
return writer
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* receiveSignedWebSocketStream
|
|
153
|
-
*
|
|
154
|
-
* Listens for StreamChunk JSON frames on a WebSocket, accumulates them, and
|
|
155
|
-
* when the final frame (f=true) arrives calls verifyStream over the complete
|
|
156
|
-
* set of collected chunks.
|
|
157
|
-
*
|
|
158
|
-
* Resolves with StreamVerifyResult when the final frame is received.
|
|
159
|
-
* Rejects if a frame cannot be decoded.
|
|
160
|
-
*/
|
|
161
|
-
export function receiveSignedWebSocketStream(
|
|
162
|
-
ws: WebSocketLike,
|
|
163
|
-
opts: StreamVerifierOpts,
|
|
164
|
-
): Promise<StreamVerifyResult> {
|
|
165
|
-
return new Promise<StreamVerifyResult>((resolve, reject) => {
|
|
166
|
-
const collected: StreamChunk[] = []
|
|
167
|
-
|
|
168
|
-
const messageListener = async (e: { data: string | Buffer }) => {
|
|
169
|
-
const raw = typeof e.data === 'string' ? e.data : e.data.toString('utf8')
|
|
170
|
-
let chunk: StreamChunk
|
|
171
|
-
try {
|
|
172
|
-
chunk = decodeStreamChunk(raw)
|
|
173
|
-
} catch (err) {
|
|
174
|
-
cleanup()
|
|
175
|
-
reject(err)
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
collected.push(chunk)
|
|
179
|
-
if (chunk.f) {
|
|
180
|
-
cleanup()
|
|
181
|
-
const result = await verifyStream(collected, opts)
|
|
182
|
-
resolve(result)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const closeListener = () => {
|
|
187
|
-
// Connection closed before receiving final frame
|
|
188
|
-
cleanup()
|
|
189
|
-
resolve({ ok: false, reason: 'connection closed before final frame' })
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function cleanup() {
|
|
193
|
-
ws.removeEventListener('message', messageListener as any)
|
|
194
|
-
ws.removeEventListener('close', closeListener as any)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
ws.addEventListener('message', messageListener as any)
|
|
198
|
-
ws.addEventListener('close', closeListener as any)
|
|
199
|
-
})
|
|
200
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2023", "DOM"],
|
|
7
|
-
"types": ["node"],
|
|
8
|
-
"strict": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"noEmit": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"verbatimModuleSyntax": false
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*.ts", "scripts/**/*.ts"]
|
|
15
|
-
}
|
package/tsconfig.lib.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"emitDeclarationOnly": true,
|
|
8
|
-
"declarationMap": false,
|
|
9
|
-
"sourceMap": false,
|
|
10
|
-
"outDir": "./dist/protocol",
|
|
11
|
-
"rootDir": "./src",
|
|
12
|
-
"stripInternal": true,
|
|
13
|
-
"strict": true,
|
|
14
|
-
"types": ["node"],
|
|
15
|
-
"skipLibCheck": true,
|
|
16
|
-
"noUnusedLocals": true,
|
|
17
|
-
"noUnusedParameters": true,
|
|
18
|
-
"noFallthroughCasesInSwitch": true,
|
|
19
|
-
"noUncheckedSideEffectImports": true
|
|
20
|
-
},
|
|
21
|
-
"include": ["src/**/*.ts"],
|
|
22
|
-
"exclude": ["src/**/*.test.ts", "src/**/*.bench.ts"]
|
|
23
|
-
}
|
package/vite.lib.config.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
build: {
|
|
5
|
-
lib: {
|
|
6
|
-
entry: 'src/index.ts',
|
|
7
|
-
formats: ['es'],
|
|
8
|
-
fileName: () => 'index.js',
|
|
9
|
-
},
|
|
10
|
-
outDir: 'dist/protocol',
|
|
11
|
-
emptyOutDir: false,
|
|
12
|
-
rollupOptions: {
|
|
13
|
-
external: [],
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
})
|