@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
|
@@ -1,556 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { DistributedReplayCache, InMemoryReplayCache } from './protocolReplay'
|
|
3
|
-
import { createEnvelope, generateEd25519KeypairBase64Url, signEnvelopeEd25519 } from './protocol'
|
|
4
|
-
import {
|
|
5
|
-
InMemoryVerificationMaterialCache,
|
|
6
|
-
SessionTransport,
|
|
7
|
-
createSignedMessage,
|
|
8
|
-
decodeEnvelopeBatch,
|
|
9
|
-
encodeEnvelope,
|
|
10
|
-
encodeEnvelopeBatch,
|
|
11
|
-
receiveEnvelope,
|
|
12
|
-
receiveEnvelopeBatch,
|
|
13
|
-
} from './protocolTransport'
|
|
14
|
-
|
|
15
|
-
describe('AIP transport and replay protection', () => {
|
|
16
|
-
it('accepts a valid signed message', async () => {
|
|
17
|
-
const envelope = await createSignedMessage({
|
|
18
|
-
sender: 'agent.alpha',
|
|
19
|
-
recipient: 'agent.beta',
|
|
20
|
-
intent: 'TASK',
|
|
21
|
-
content: 'route:optimize',
|
|
22
|
-
messageId: 'tx-1',
|
|
23
|
-
nonce: 'n-1',
|
|
24
|
-
nowMs: 1000,
|
|
25
|
-
ttlMs: 60_000,
|
|
26
|
-
secret: 'shared-secret',
|
|
27
|
-
keyId: 'k1',
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const result = await receiveEnvelope(encodeEnvelope(envelope), {
|
|
31
|
-
nowMs: 2000,
|
|
32
|
-
replayCache: new InMemoryReplayCache(),
|
|
33
|
-
secretResolver: async (keyId, sender) => (keyId === 'k1' && sender === 'agent.alpha' ? 'shared-secret' : undefined),
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
expect(result.ok).toBe(true)
|
|
37
|
-
expect(result.diagnostics.filter((d) => d.level === 'error')).toHaveLength(0)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('accepts compact wire format', async () => {
|
|
41
|
-
const envelope = await createSignedMessage({
|
|
42
|
-
sender: 'agent.alpha',
|
|
43
|
-
recipient: 'agent.beta',
|
|
44
|
-
intent: 'TASK',
|
|
45
|
-
content: 'route:compact',
|
|
46
|
-
messageId: 'tx-c1',
|
|
47
|
-
nonce: 'n-c1',
|
|
48
|
-
nowMs: 1000,
|
|
49
|
-
ttlMs: 60_000,
|
|
50
|
-
secret: 'shared-secret',
|
|
51
|
-
keyId: 'k1',
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const result = await receiveEnvelope(encodeEnvelope(envelope, 'compact'), {
|
|
55
|
-
nowMs: 1001,
|
|
56
|
-
replayCache: new InMemoryReplayCache(),
|
|
57
|
-
secretResolver: async () => 'shared-secret',
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
expect(result.ok).toBe(true)
|
|
61
|
-
expect(result.envelope?.body.content).toBe('route:compact')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('accepts binary wire format', async () => {
|
|
65
|
-
const envelope = await createSignedMessage({
|
|
66
|
-
sender: 'agent.alpha',
|
|
67
|
-
recipient: 'agent.beta',
|
|
68
|
-
intent: 'TASK',
|
|
69
|
-
content: 'route:binary-transport',
|
|
70
|
-
messageId: 'tx-bin-1',
|
|
71
|
-
nonce: 'n-bin-1',
|
|
72
|
-
nowMs: 1000,
|
|
73
|
-
ttlMs: 60_000,
|
|
74
|
-
secret: 'shared-secret',
|
|
75
|
-
keyId: 'k1',
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
const raw = encodeEnvelope(envelope, 'binary')
|
|
79
|
-
expect(raw).toBeInstanceOf(Uint8Array)
|
|
80
|
-
|
|
81
|
-
const result = await receiveEnvelope(raw, {
|
|
82
|
-
nowMs: 1001,
|
|
83
|
-
replayCache: new InMemoryReplayCache(),
|
|
84
|
-
secretResolver: async () => 'shared-secret',
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
expect(result.ok).toBe(true)
|
|
88
|
-
expect(result.envelope).toEqual(envelope)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('receives a compact batch and validates each message', async () => {
|
|
92
|
-
const first = await createSignedMessage({
|
|
93
|
-
sender: 'agent.alpha',
|
|
94
|
-
recipient: 'agent.beta',
|
|
95
|
-
intent: 'PING',
|
|
96
|
-
content: 'one',
|
|
97
|
-
messageId: 'tx-b1',
|
|
98
|
-
nonce: 'n-b1',
|
|
99
|
-
nowMs: 1000,
|
|
100
|
-
ttlMs: 60_000,
|
|
101
|
-
secret: 'shared-secret',
|
|
102
|
-
keyId: 'k1',
|
|
103
|
-
})
|
|
104
|
-
const second = await createSignedMessage({
|
|
105
|
-
sender: 'agent.alpha',
|
|
106
|
-
recipient: 'agent.beta',
|
|
107
|
-
intent: 'PING',
|
|
108
|
-
content: 'two',
|
|
109
|
-
messageId: 'tx-b2',
|
|
110
|
-
nonce: 'n-b2',
|
|
111
|
-
nowMs: 1001,
|
|
112
|
-
ttlMs: 60_000,
|
|
113
|
-
secret: 'shared-secret',
|
|
114
|
-
keyId: 'k1',
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const batchRaw = encodeEnvelopeBatch([first, second], 'compact')
|
|
118
|
-
const decodedBatch = decodeEnvelopeBatch(batchRaw)
|
|
119
|
-
expect(decodedBatch).toHaveLength(2)
|
|
120
|
-
expect(decodedBatch.every((item) => item.ok)).toBe(true)
|
|
121
|
-
|
|
122
|
-
const results = await receiveEnvelopeBatch(batchRaw, {
|
|
123
|
-
nowMs: 1100,
|
|
124
|
-
replayCache: new InMemoryReplayCache(),
|
|
125
|
-
secretResolver: async () => 'shared-secret',
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
expect(results).toHaveLength(2)
|
|
129
|
-
expect(results.every((item) => item.ok)).toBe(true)
|
|
130
|
-
expect(results[0]?.envelope?.body.content).toBe('one')
|
|
131
|
-
expect(results[1]?.envelope?.body.content).toBe('two')
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('receives mixed binary and compact array batches in input order', async () => {
|
|
135
|
-
const first = await createSignedMessage({
|
|
136
|
-
sender: 'agent.alpha',
|
|
137
|
-
recipient: 'agent.beta',
|
|
138
|
-
intent: 'PING',
|
|
139
|
-
content: 'binary-one',
|
|
140
|
-
messageId: 'tx-mix-1',
|
|
141
|
-
nonce: 'n-mix-1',
|
|
142
|
-
nowMs: 1000,
|
|
143
|
-
ttlMs: 60_000,
|
|
144
|
-
secret: 'shared-secret',
|
|
145
|
-
keyId: 'k1',
|
|
146
|
-
})
|
|
147
|
-
const second = await createSignedMessage({
|
|
148
|
-
sender: 'agent.alpha',
|
|
149
|
-
recipient: 'agent.beta',
|
|
150
|
-
intent: 'PING',
|
|
151
|
-
content: 'compact-two',
|
|
152
|
-
messageId: 'tx-mix-2',
|
|
153
|
-
nonce: 'n-mix-2',
|
|
154
|
-
nowMs: 1001,
|
|
155
|
-
ttlMs: 60_000,
|
|
156
|
-
secret: 'shared-secret',
|
|
157
|
-
keyId: 'k1',
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
const results = await receiveEnvelopeBatch([encodeEnvelope(first, 'binary'), encodeEnvelope(second, 'compact')], {
|
|
161
|
-
nowMs: 1100,
|
|
162
|
-
replayCache: new InMemoryReplayCache(),
|
|
163
|
-
secretResolver: async () => 'shared-secret',
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
expect(results).toHaveLength(2)
|
|
167
|
-
expect(results.every((item) => item.ok)).toBe(true)
|
|
168
|
-
expect(results[0]?.envelope?.body.content).toBe('binary-one')
|
|
169
|
-
expect(results[1]?.envelope?.body.content).toBe('compact-two')
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('limits batch receive concurrency while preserving result order', async () => {
|
|
173
|
-
const envelopes = await Promise.all(
|
|
174
|
-
[1, 2, 3].map((index) =>
|
|
175
|
-
createSignedMessage({
|
|
176
|
-
sender: 'agent.alpha',
|
|
177
|
-
recipient: 'agent.beta',
|
|
178
|
-
intent: 'PING',
|
|
179
|
-
content: `bounded-${index}`,
|
|
180
|
-
messageId: `tx-bounded-${index}`,
|
|
181
|
-
nonce: `n-bounded-${index}`,
|
|
182
|
-
nowMs: 1000 + index,
|
|
183
|
-
ttlMs: 60_000,
|
|
184
|
-
secret: 'shared-secret',
|
|
185
|
-
keyId: 'k1',
|
|
186
|
-
}),
|
|
187
|
-
),
|
|
188
|
-
)
|
|
189
|
-
let active = 0
|
|
190
|
-
let maxActive = 0
|
|
191
|
-
const results = await receiveEnvelopeBatch(envelopes, {
|
|
192
|
-
nowMs: 1100,
|
|
193
|
-
replayCache: new InMemoryReplayCache(),
|
|
194
|
-
batchConcurrency: 1,
|
|
195
|
-
secretResolver: async () => {
|
|
196
|
-
active += 1
|
|
197
|
-
maxActive = Math.max(maxActive, active)
|
|
198
|
-
await Promise.resolve()
|
|
199
|
-
active -= 1
|
|
200
|
-
return 'shared-secret'
|
|
201
|
-
},
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
expect(results.map((result) => result.envelope?.body.content)).toEqual(['bounded-1', 'bounded-2', 'bounded-3'])
|
|
205
|
-
expect(maxActive).toBe(1)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('rejects duplicate envelopes inside the same batch', async () => {
|
|
209
|
-
const envelope = await createSignedMessage({
|
|
210
|
-
sender: 'agent.alpha',
|
|
211
|
-
recipient: 'agent.beta',
|
|
212
|
-
intent: 'PING',
|
|
213
|
-
content: 'duplicate-batch',
|
|
214
|
-
messageId: 'tx-duplicate-batch',
|
|
215
|
-
nonce: 'n-duplicate-batch',
|
|
216
|
-
nowMs: 1000,
|
|
217
|
-
ttlMs: 60_000,
|
|
218
|
-
secret: 'shared-secret',
|
|
219
|
-
keyId: 'k1',
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
const results = await receiveEnvelopeBatch([envelope, envelope], {
|
|
223
|
-
nowMs: 1100,
|
|
224
|
-
replayCache: new InMemoryReplayCache(),
|
|
225
|
-
batchConcurrency: 2,
|
|
226
|
-
secretResolver: async () => 'shared-secret',
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
expect(results.filter((result) => result.ok)).toHaveLength(1)
|
|
230
|
-
expect(results.filter((result) => !result.ok)[0]?.diagnostics.some((d) => d.message.includes('Replay detected'))).toBe(true)
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('emits batch summary telemetry', async () => {
|
|
234
|
-
const events: string[] = []
|
|
235
|
-
const envelope = await createSignedMessage({
|
|
236
|
-
sender: 'agent.alpha',
|
|
237
|
-
recipient: 'agent.beta',
|
|
238
|
-
intent: 'PING',
|
|
239
|
-
content: 'batch-telemetry',
|
|
240
|
-
messageId: 'tx-batch-telemetry',
|
|
241
|
-
nonce: 'n-batch-telemetry',
|
|
242
|
-
nowMs: 1000,
|
|
243
|
-
ttlMs: 60_000,
|
|
244
|
-
secret: 'shared-secret',
|
|
245
|
-
keyId: 'k1',
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
await receiveEnvelopeBatch([envelope], {
|
|
249
|
-
nowMs: 1100,
|
|
250
|
-
replayCache: new InMemoryReplayCache(),
|
|
251
|
-
secretResolver: async () => 'shared-secret',
|
|
252
|
-
telemetry: async (event) => {
|
|
253
|
-
events.push(event.phase)
|
|
254
|
-
},
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
expect(events).toContain('batch_summary')
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('shares replay protection across a long-lived session transport', async () => {
|
|
261
|
-
const envelope = await createSignedMessage({
|
|
262
|
-
sender: 'agent.alpha',
|
|
263
|
-
recipient: 'agent.beta',
|
|
264
|
-
intent: 'TASK',
|
|
265
|
-
content: 'route:session',
|
|
266
|
-
messageId: 'tx-s1',
|
|
267
|
-
nonce: 'n-s1',
|
|
268
|
-
nowMs: 1000,
|
|
269
|
-
ttlMs: 60_000,
|
|
270
|
-
secret: 'shared-secret',
|
|
271
|
-
keyId: 'k1',
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
const transport = new SessionTransport({
|
|
275
|
-
secretResolver: async () => 'shared-secret',
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
const first = await transport.receive(envelope, 1001)
|
|
279
|
-
const second = await transport.receive(envelope, 1002)
|
|
280
|
-
|
|
281
|
-
expect(first.ok).toBe(true)
|
|
282
|
-
expect(second.ok).toBe(false)
|
|
283
|
-
expect(second.diagnostics.some((d) => d.message.includes('Replay detected'))).toBe(true)
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
it('caches resolved verification material across session receives', async () => {
|
|
287
|
-
const first = await createSignedMessage({
|
|
288
|
-
sender: 'agent.alpha',
|
|
289
|
-
recipient: 'agent.beta',
|
|
290
|
-
intent: 'TASK',
|
|
291
|
-
content: 'route:cache-1',
|
|
292
|
-
messageId: 'tx-vc-1',
|
|
293
|
-
nonce: 'n-vc-1',
|
|
294
|
-
nowMs: 1000,
|
|
295
|
-
ttlMs: 60_000,
|
|
296
|
-
secret: 'shared-secret',
|
|
297
|
-
keyId: 'k-cache',
|
|
298
|
-
})
|
|
299
|
-
const second = await createSignedMessage({
|
|
300
|
-
sender: 'agent.alpha',
|
|
301
|
-
recipient: 'agent.beta',
|
|
302
|
-
intent: 'TASK',
|
|
303
|
-
content: 'route:cache-2',
|
|
304
|
-
messageId: 'tx-vc-2',
|
|
305
|
-
nonce: 'n-vc-2',
|
|
306
|
-
nowMs: 1001,
|
|
307
|
-
ttlMs: 60_000,
|
|
308
|
-
secret: 'shared-secret',
|
|
309
|
-
keyId: 'k-cache',
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
let resolverCalls = 0
|
|
313
|
-
const transport = new SessionTransport({
|
|
314
|
-
verificationMaterialCache: new InMemoryVerificationMaterialCache(),
|
|
315
|
-
secretResolver: async () => {
|
|
316
|
-
resolverCalls += 1
|
|
317
|
-
return 'shared-secret'
|
|
318
|
-
},
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
const r1 = await transport.receive(first, 1002)
|
|
322
|
-
const r2 = await transport.receive(second, 1003)
|
|
323
|
-
|
|
324
|
-
expect(r1.ok).toBe(true)
|
|
325
|
-
expect(r2.ok).toBe(true)
|
|
326
|
-
expect(resolverCalls).toBe(1)
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
it('rejects replayed envelope', async () => {
|
|
330
|
-
const envelope = await createSignedMessage({
|
|
331
|
-
sender: 'agent.alpha',
|
|
332
|
-
intent: 'PING',
|
|
333
|
-
content: 'hello',
|
|
334
|
-
messageId: 'tx-2',
|
|
335
|
-
nonce: 'n-2',
|
|
336
|
-
nowMs: 1000,
|
|
337
|
-
ttlMs: 60_000,
|
|
338
|
-
secret: 'shared-secret',
|
|
339
|
-
keyId: 'k1',
|
|
340
|
-
})
|
|
341
|
-
const cache = new InMemoryReplayCache()
|
|
342
|
-
const options = {
|
|
343
|
-
nowMs: 2000,
|
|
344
|
-
replayCache: cache,
|
|
345
|
-
secretResolver: async () => 'shared-secret',
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const first = await receiveEnvelope(envelope, options)
|
|
349
|
-
const second = await receiveEnvelope(envelope, options)
|
|
350
|
-
|
|
351
|
-
expect(first.ok).toBe(true)
|
|
352
|
-
expect(second.ok).toBe(false)
|
|
353
|
-
expect(second.diagnostics.some((d) => d.message.includes('Replay detected'))).toBe(true)
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
it('rejects invalid signature', async () => {
|
|
357
|
-
const envelope = await createSignedMessage({
|
|
358
|
-
sender: 'agent.alpha',
|
|
359
|
-
intent: 'RESULT',
|
|
360
|
-
content: 'done',
|
|
361
|
-
messageId: 'tx-3',
|
|
362
|
-
nonce: 'n-3',
|
|
363
|
-
nowMs: 1000,
|
|
364
|
-
ttlMs: 60_000,
|
|
365
|
-
secret: 'real-secret',
|
|
366
|
-
keyId: 'k1',
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
const result = await receiveEnvelope(envelope, {
|
|
370
|
-
nowMs: 2000,
|
|
371
|
-
replayCache: new InMemoryReplayCache(),
|
|
372
|
-
secretResolver: async () => 'wrong-secret',
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
expect(result.ok).toBe(false)
|
|
376
|
-
expect(result.diagnostics.some((d) => d.message === 'Signature verification failed')).toBe(true)
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
it('does not reserve replay keys for invalid signatures', async () => {
|
|
380
|
-
const envelope = await createSignedMessage({
|
|
381
|
-
sender: 'agent.alpha',
|
|
382
|
-
intent: 'RESULT',
|
|
383
|
-
content: 'valid-after-invalid',
|
|
384
|
-
messageId: 'tx-bad-first',
|
|
385
|
-
nonce: 'n-bad-first',
|
|
386
|
-
nowMs: 1000,
|
|
387
|
-
ttlMs: 60_000,
|
|
388
|
-
secret: 'real-secret',
|
|
389
|
-
keyId: 'k1',
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
const replayCache = new InMemoryReplayCache()
|
|
393
|
-
const bad = await receiveEnvelope(envelope, {
|
|
394
|
-
nowMs: 2000,
|
|
395
|
-
replayCache,
|
|
396
|
-
secretResolver: async () => 'wrong-secret',
|
|
397
|
-
})
|
|
398
|
-
const good = await receiveEnvelope(envelope, {
|
|
399
|
-
nowMs: 2001,
|
|
400
|
-
replayCache,
|
|
401
|
-
secretResolver: async () => 'real-secret',
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
expect(bad.ok).toBe(false)
|
|
405
|
-
expect(bad.diagnostics.some((d) => d.message === 'Signature verification failed')).toBe(true)
|
|
406
|
-
expect(good.ok).toBe(true)
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
it('supports async distributed replay cache semantics', async () => {
|
|
410
|
-
const envelope = await createSignedMessage({
|
|
411
|
-
sender: 'agent.alpha',
|
|
412
|
-
intent: 'PING',
|
|
413
|
-
content: 'hello-distributed',
|
|
414
|
-
messageId: 'tx-dist-1',
|
|
415
|
-
nonce: 'n-dist-1',
|
|
416
|
-
nowMs: 1000,
|
|
417
|
-
ttlMs: 60_000,
|
|
418
|
-
secret: 'shared-secret',
|
|
419
|
-
keyId: 'k1',
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
const reserved = new Set<string>()
|
|
423
|
-
const replayCache = new DistributedReplayCache({
|
|
424
|
-
reserve: async (key) => {
|
|
425
|
-
if (reserved.has(key)) return false
|
|
426
|
-
reserved.add(key)
|
|
427
|
-
return true
|
|
428
|
-
},
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
const first = await receiveEnvelope(envelope, {
|
|
432
|
-
nowMs: 2000,
|
|
433
|
-
replayCache,
|
|
434
|
-
secretResolver: async () => 'shared-secret',
|
|
435
|
-
})
|
|
436
|
-
const second = await receiveEnvelope(envelope, {
|
|
437
|
-
nowMs: 2001,
|
|
438
|
-
replayCache,
|
|
439
|
-
secretResolver: async () => 'shared-secret',
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
expect(first.ok).toBe(true)
|
|
443
|
-
expect(second.ok).toBe(false)
|
|
444
|
-
expect(second.diagnostics.some((d) => d.message.includes('Replay detected'))).toBe(true)
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
it('accepts Ed25519 signatures via signatureResolver', async () => {
|
|
448
|
-
const keys = await generateEd25519KeypairBase64Url()
|
|
449
|
-
const unsigned = createEnvelope({
|
|
450
|
-
sender: 'agent.alpha',
|
|
451
|
-
intent: 'TASK',
|
|
452
|
-
content: 'route:ed25519',
|
|
453
|
-
messageId: 'tx-ed1',
|
|
454
|
-
nonce: 'n-ed1',
|
|
455
|
-
nowMs: 1000,
|
|
456
|
-
ttlMs: 60_000,
|
|
457
|
-
})
|
|
458
|
-
const envelope = await signEnvelopeEd25519(unsigned, keys.privateKey, 'ed-k1')
|
|
459
|
-
|
|
460
|
-
const result = await receiveEnvelope(encodeEnvelope(envelope, 'compact'), {
|
|
461
|
-
nowMs: 1001,
|
|
462
|
-
replayCache: new InMemoryReplayCache(),
|
|
463
|
-
signatureResolver: async (signature) =>
|
|
464
|
-
signature.alg === 'ED25519' && signature.keyId === 'ed-k1' ? { alg: 'ED25519', publicKey: keys.publicKey } : undefined,
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
expect(result.ok).toBe(true)
|
|
468
|
-
expect(result.envelope?.signature?.alg).toBe('ED25519')
|
|
469
|
-
})
|
|
470
|
-
|
|
471
|
-
it('rejects ED25519 signatures when resolver material is missing', async () => {
|
|
472
|
-
const keys = await generateEd25519KeypairBase64Url()
|
|
473
|
-
const unsigned = createEnvelope({
|
|
474
|
-
sender: 'agent.alpha',
|
|
475
|
-
intent: 'TASK',
|
|
476
|
-
content: 'route:ed25519-missing',
|
|
477
|
-
messageId: 'tx-ed2',
|
|
478
|
-
nonce: 'n-ed2',
|
|
479
|
-
nowMs: 1000,
|
|
480
|
-
ttlMs: 60_000,
|
|
481
|
-
})
|
|
482
|
-
const envelope = await signEnvelopeEd25519(unsigned, keys.privateKey, 'ed-k2')
|
|
483
|
-
|
|
484
|
-
const result = await receiveEnvelope(encodeEnvelope(envelope), {
|
|
485
|
-
nowMs: 1001,
|
|
486
|
-
replayCache: new InMemoryReplayCache(),
|
|
487
|
-
secretResolver: async () => 'unused-secret',
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
expect(result.ok).toBe(false)
|
|
491
|
-
expect(result.diagnostics.some((d) => d.message.includes('No signature verification material'))).toBe(true)
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
it('rejects messages that exceed configured future clock skew', async () => {
|
|
495
|
-
const envelope = await createSignedMessage({
|
|
496
|
-
sender: 'agent.alpha',
|
|
497
|
-
intent: 'PING',
|
|
498
|
-
content: 'clock-skew',
|
|
499
|
-
messageId: 'tx-skew-1',
|
|
500
|
-
nonce: 'n-skew-1',
|
|
501
|
-
nowMs: 5000,
|
|
502
|
-
ttlMs: 60_000,
|
|
503
|
-
secret: 'shared-secret',
|
|
504
|
-
keyId: 'k1',
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
const result = await receiveEnvelope(envelope, {
|
|
508
|
-
nowMs: 1000,
|
|
509
|
-
maxClockSkewMs: 200,
|
|
510
|
-
replayCache: new InMemoryReplayCache(),
|
|
511
|
-
secretResolver: async () => 'shared-secret',
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
expect(result.ok).toBe(false)
|
|
515
|
-
expect(result.diagnostics.some((d) => d.message === 'Message timestamp exceeds allowed clock skew')).toBe(true)
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
it('emits telemetry events for reject and accept paths', async () => {
|
|
519
|
-
const events: string[] = []
|
|
520
|
-
const envelope = await createSignedMessage({
|
|
521
|
-
sender: 'agent.alpha',
|
|
522
|
-
intent: 'TASK',
|
|
523
|
-
content: 'telemetry',
|
|
524
|
-
messageId: 'tx-tele-1',
|
|
525
|
-
nonce: 'n-tele-1',
|
|
526
|
-
nowMs: 1000,
|
|
527
|
-
ttlMs: 60_000,
|
|
528
|
-
secret: 'shared-secret',
|
|
529
|
-
keyId: 'k1',
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
const bad = await receiveEnvelope(envelope, {
|
|
533
|
-
nowMs: 1001,
|
|
534
|
-
replayCache: new InMemoryReplayCache(),
|
|
535
|
-
secretResolver: async () => 'wrong-secret',
|
|
536
|
-
telemetry: async (event) => {
|
|
537
|
-
events.push(event.phase)
|
|
538
|
-
},
|
|
539
|
-
})
|
|
540
|
-
expect(bad.ok).toBe(false)
|
|
541
|
-
|
|
542
|
-
const good = await receiveEnvelope(envelope, {
|
|
543
|
-
nowMs: 1002,
|
|
544
|
-
replayCache: new InMemoryReplayCache(),
|
|
545
|
-
secretResolver: async () => 'shared-secret',
|
|
546
|
-
telemetry: async (event) => {
|
|
547
|
-
events.push(event.phase)
|
|
548
|
-
},
|
|
549
|
-
})
|
|
550
|
-
expect(good.ok).toBe(true)
|
|
551
|
-
|
|
552
|
-
expect(events).toContain('decoded')
|
|
553
|
-
expect(events).toContain('rejected_bad_signature')
|
|
554
|
-
expect(events).toContain('accepted')
|
|
555
|
-
})
|
|
556
|
-
})
|