@7h3/protocol 0.4.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 -323
- 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 -79
- package/policyEnforcer.d.ts +50 -0
- package/policyTelemetryFeedback.d.ts +11 -0
- package/protocol.d.ts +66 -0
- 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 -92
- 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 -385
- 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.png +0 -0
- 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/go.mod +0 -3
- 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/webhook.go +0 -136
- package/sdk/python/README.md +0 -18
- package/sdk/python/protocol_7h3/__init__.py +0 -46
- package/sdk/python/protocol_7h3/http.py +0 -212
- package/sdk/python/protocol_7h3/keys.py +0 -149
- package/sdk/python/protocol_7h3/protocol.py +0 -525
- package/sdk/python/protocol_7h3/queue.py +0 -118
- 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_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/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/conformance.test.ts +0 -136
- package/src/conformanceVectors.ts +0 -99
- 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 -171
- 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 -163
- package/src/index.ts +0 -32
- 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/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.test.ts +0 -155
- package/src/protocol.ts +0 -413
- 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 -141
- package/src/replayStores.ts +0 -82
- 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/webhookBinding.test.ts +0 -144
- package/src/webhookBinding.ts +0 -115
- package/src/wsBinding.test.ts +0 -221
- package/src/wsBinding.ts +0 -100
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -23
- package/vite.lib.config.ts +0 -16
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
interface JsonRpcEnvelope {
|
|
5
|
-
jsonrpc: '2.0'
|
|
6
|
-
id: string | number | null
|
|
7
|
-
result?: unknown
|
|
8
|
-
error?: {
|
|
9
|
-
code: number
|
|
10
|
-
message: string
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function waitForLine(child: ChildProcessWithoutNullStreams, timeoutMs = 8_000): Promise<string> {
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
let buffer = ''
|
|
17
|
-
const timer = setTimeout(() => {
|
|
18
|
-
cleanup()
|
|
19
|
-
reject(new Error('Timed out waiting for gateway output'))
|
|
20
|
-
}, timeoutMs)
|
|
21
|
-
|
|
22
|
-
const onData = (chunk: Buffer) => {
|
|
23
|
-
buffer += chunk.toString('utf8')
|
|
24
|
-
const newlineIndex = buffer.indexOf('\n')
|
|
25
|
-
if (newlineIndex >= 0) {
|
|
26
|
-
const line = buffer.slice(0, newlineIndex).trim()
|
|
27
|
-
cleanup()
|
|
28
|
-
resolve(line)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const onError = (error: Error) => {
|
|
33
|
-
cleanup()
|
|
34
|
-
reject(error)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const onExit = () => {
|
|
38
|
-
cleanup()
|
|
39
|
-
reject(new Error('Gateway process exited before producing output'))
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const cleanup = () => {
|
|
43
|
-
clearTimeout(timer)
|
|
44
|
-
child.stdout.off('data', onData)
|
|
45
|
-
child.off('error', onError)
|
|
46
|
-
child.off('exit', onExit)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
child.stdout.on('data', onData)
|
|
50
|
-
child.on('error', onError)
|
|
51
|
-
child.on('exit', onExit)
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function startGateway(extraEnv: Record<string, string> = {}): ChildProcessWithoutNullStreams {
|
|
56
|
-
const child = spawn('npm', ['run', '-s', 'aip:mcp:gateway'], {
|
|
57
|
-
cwd: process.cwd(),
|
|
58
|
-
env: {
|
|
59
|
-
...process.env,
|
|
60
|
-
...extraEnv,
|
|
61
|
-
},
|
|
62
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
child.stderr.on('data', () => {
|
|
66
|
-
// suppress stderr accumulation in test output
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
return child
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function stopGateway(child: ChildProcessWithoutNullStreams): Promise<void> {
|
|
73
|
-
if (child.killed) return
|
|
74
|
-
child.kill('SIGTERM')
|
|
75
|
-
await new Promise<void>((resolve) => {
|
|
76
|
-
child.once('exit', () => resolve())
|
|
77
|
-
setTimeout(() => resolve(), 2_000)
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe('AIP MCP gateway CLI', () => {
|
|
82
|
-
it('returns JSON-RPC result for allowed method', async () => {
|
|
83
|
-
const child = startGateway()
|
|
84
|
-
try {
|
|
85
|
-
child.stdin.write(
|
|
86
|
-
`${JSON.stringify({ jsonrpc: '2.0', id: 99, method: 'tools/call', params: { name: 'planner' } })}\n`,
|
|
87
|
-
)
|
|
88
|
-
const line = await waitForLine(child)
|
|
89
|
-
const response = JSON.parse(line) as JsonRpcEnvelope
|
|
90
|
-
|
|
91
|
-
expect(response.jsonrpc).toBe('2.0')
|
|
92
|
-
expect(response.id).toBe(99)
|
|
93
|
-
expect(response.error).toBeUndefined()
|
|
94
|
-
expect(response.result).toBeTruthy()
|
|
95
|
-
} finally {
|
|
96
|
-
await stopGateway(child)
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('returns method-not-found for disallowed method from env policy', async () => {
|
|
101
|
-
const child = startGateway({ AIP_ALLOWED_METHODS: 'tools/call' })
|
|
102
|
-
try {
|
|
103
|
-
child.stdin.write(
|
|
104
|
-
`${JSON.stringify({ jsonrpc: '2.0', id: 'abc', method: 'resources/read', params: { uri: 'file://x' } })}\n`,
|
|
105
|
-
)
|
|
106
|
-
const line = await waitForLine(child)
|
|
107
|
-
const response = JSON.parse(line) as JsonRpcEnvelope
|
|
108
|
-
|
|
109
|
-
expect(response.id).toBe('abc')
|
|
110
|
-
expect(response.result).toBeUndefined()
|
|
111
|
-
expect(response.error?.code).toBe(-32601)
|
|
112
|
-
} finally {
|
|
113
|
-
await stopGateway(child)
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
})
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile, copyFile, readdir } from 'node:fs/promises'
|
|
2
|
-
import process from 'node:process'
|
|
3
|
-
|
|
4
|
-
interface RootPackageJson {
|
|
5
|
-
version?: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function parseVersionArg(argv: string[]): string | undefined {
|
|
9
|
-
const idx = argv.indexOf('--version')
|
|
10
|
-
if (idx < 0) return undefined
|
|
11
|
-
return argv[idx + 1]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function readRootVersion(): Promise<string> {
|
|
15
|
-
const raw = await readFile('package.json', 'utf8')
|
|
16
|
-
const parsed = JSON.parse(raw) as RootPackageJson
|
|
17
|
-
return parsed.version ?? '0.0.0'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function main(): Promise<void> {
|
|
21
|
-
const version = parseVersionArg(process.argv) ?? (await readRootVersion())
|
|
22
|
-
const outDir = 'dist/npm-protocol'
|
|
23
|
-
|
|
24
|
-
await mkdir(outDir, { recursive: true })
|
|
25
|
-
await copyFile('dist/protocol/index.js', `${outDir}/index.js`)
|
|
26
|
-
|
|
27
|
-
// Copy all .d.ts files — index.d.ts re-exports from the individual module files,
|
|
28
|
-
// so all of them must be present for TypeScript consumers to resolve types.
|
|
29
|
-
const distFiles = await readdir('dist/protocol')
|
|
30
|
-
for (const file of distFiles) {
|
|
31
|
-
if (file.endsWith('.d.ts')) {
|
|
32
|
-
await copyFile(`dist/protocol/${file}`, `${outDir}/${file}`)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const packageJson = {
|
|
37
|
-
name: '@7h3/protocol',
|
|
38
|
-
version,
|
|
39
|
-
description: '7h3 Protocol: deterministic, signed, replay-safe AI-to-AI message envelopes (wire 7h3/0.1).',
|
|
40
|
-
type: 'module',
|
|
41
|
-
main: './index.js',
|
|
42
|
-
types: './index.d.ts',
|
|
43
|
-
exports: {
|
|
44
|
-
'.': {
|
|
45
|
-
import: './index.js',
|
|
46
|
-
types: './index.d.ts',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
files: ['index.js', '*.d.ts', 'README.md'],
|
|
50
|
-
license: 'MIT',
|
|
51
|
-
repository: {
|
|
52
|
-
type: 'git',
|
|
53
|
-
url: 'https://github.com/IceMasterT/7h3-protocol-aip.git',
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const readme = [
|
|
58
|
-
'# @7h3/protocol',
|
|
59
|
-
'',
|
|
60
|
-
'7h3 Protocol (wire version `7h3/0.1`):',
|
|
61
|
-
'deterministic, signed, replay-safe AI-to-AI message envelopes.',
|
|
62
|
-
'',
|
|
63
|
-
'Install:',
|
|
64
|
-
'',
|
|
65
|
-
'```bash',
|
|
66
|
-
'npm install @7h3/protocol',
|
|
67
|
-
'```',
|
|
68
|
-
'',
|
|
69
|
-
'Import:',
|
|
70
|
-
'',
|
|
71
|
-
'```ts',
|
|
72
|
-
"import { createAipAgentAdapter, receiveEnvelope } from '@7h3/protocol'",
|
|
73
|
-
'```',
|
|
74
|
-
'',
|
|
75
|
-
].join('\n')
|
|
76
|
-
|
|
77
|
-
await writeFile(`${outDir}/package.json`, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8')
|
|
78
|
-
await writeFile(`${outDir}/README.md`, readme, 'utf8')
|
|
79
|
-
|
|
80
|
-
console.log(`Prepared publishable package in ${outDir}`)
|
|
81
|
-
console.log(`Version: ${version}`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
main().catch((error: unknown) => {
|
|
85
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
86
|
-
console.error(message)
|
|
87
|
-
process.exitCode = 1
|
|
88
|
-
})
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { signCanonicalPayloadHmac, signCanonicalPayloadEd25519 } from '../src/protocol'
|
|
2
|
-
import { AIP_V01_CONFORMANCE_VECTORS, AIP_V01_ED25519_CONFORMANCE_VECTORS } from '../src/conformanceVectors'
|
|
3
|
-
|
|
4
|
-
async function main() {
|
|
5
|
-
console.log('=== HMAC vectors ===')
|
|
6
|
-
for (const v of AIP_V01_CONFORMANCE_VECTORS) {
|
|
7
|
-
const sig = await signCanonicalPayloadHmac(v.canonical, v.secret)
|
|
8
|
-
console.log(`${v.id}: '${sig}'`)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
console.log('\n=== Ed25519 vectors ===')
|
|
12
|
-
for (const v of AIP_V01_ED25519_CONFORMANCE_VECTORS) {
|
|
13
|
-
const sig = await signCanonicalPayloadEd25519(v.canonical, v.privateKey)
|
|
14
|
-
console.log(`${v.id}: '${sig}'`)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
main().catch(e => { console.error(e); process.exit(1) })
|
package/scripts/release-gate.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
const commands: Array<[string, string[]]> = [
|
|
4
|
-
['npm', ['run', 'policy:validate']],
|
|
5
|
-
['npm', ['run', 'test']],
|
|
6
|
-
['npm', ['run', 'build:protocol']],
|
|
7
|
-
['npm', ['run', 'lint']],
|
|
8
|
-
['npm', ['run', 'bench:wire:quick']],
|
|
9
|
-
['npm', ['run', 'bench:openloop:adaptive:ci']],
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
for (const [command, args] of commands) {
|
|
13
|
-
console.log(`\n$ ${command} ${args.join(' ')}`)
|
|
14
|
-
const result = spawnSync(command, args, { stdio: 'inherit', shell: false })
|
|
15
|
-
if (result.status !== 0) {
|
|
16
|
-
process.exitCode = result.status ?? 1
|
|
17
|
-
break
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
-
import { parseRuntimePolicyJson } from '../src/runtimePolicy'
|
|
3
|
-
|
|
4
|
-
async function main(): Promise<void> {
|
|
5
|
-
const path = process.argv[2] ?? 'AI_RUNTIME_POLICY.json'
|
|
6
|
-
const text = await readFile(path, 'utf8')
|
|
7
|
-
const policy = parseRuntimePolicyJson(text)
|
|
8
|
-
if (!policy.hard_invariants || !policy.slo_defaults || !policy.tuning) {
|
|
9
|
-
throw new Error('Runtime policy is missing one or more required sections: hard_invariants, slo_defaults, tuning')
|
|
10
|
-
}
|
|
11
|
-
console.log(`Runtime policy validated: ${policy.name}@${policy.version} (${policy.status})`)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
main().catch((error: unknown) => {
|
|
15
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
16
|
-
console.error(message)
|
|
17
|
-
process.exitCode = 1
|
|
18
|
-
})
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
generateKeypair,
|
|
4
|
-
canonicalizeEnvelope,
|
|
5
|
-
createEnvelope,
|
|
6
|
-
signEnvelope,
|
|
7
|
-
verifyEnvelope,
|
|
8
|
-
signRequest,
|
|
9
|
-
isEnvelopeExpired,
|
|
10
|
-
ENVELOPE_HEADER,
|
|
11
|
-
WIRE_VERSION,
|
|
12
|
-
type BrowserEnvelopeHeader,
|
|
13
|
-
type BrowserEnvelopeBody,
|
|
14
|
-
} from './index.ts'
|
|
15
|
-
|
|
16
|
-
describe('Browser SDK', () => {
|
|
17
|
-
// Test 1: generateKeypair produces non-empty strings
|
|
18
|
-
it('generateKeypair produces non-empty strings', async () => {
|
|
19
|
-
const kp = await generateKeypair()
|
|
20
|
-
expect(typeof kp.publicKey).toBe('string')
|
|
21
|
-
expect(kp.publicKey.length).toBeGreaterThan(0)
|
|
22
|
-
expect(typeof kp.privateKey).toBe('string')
|
|
23
|
-
expect(kp.privateKey.length).toBeGreaterThan(0)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// Test 2: canonicalizeEnvelope matches exact conformance vector
|
|
27
|
-
it('canonicalizeEnvelope matches conformance vector', () => {
|
|
28
|
-
const header: BrowserEnvelopeHeader = {
|
|
29
|
-
version: '7h3/0.1',
|
|
30
|
-
messageId: 'vec-1',
|
|
31
|
-
nonce: 'nonce-vec-1',
|
|
32
|
-
sender: 'agent.alpha',
|
|
33
|
-
recipient: 'agent.beta',
|
|
34
|
-
timestampMs: 1712500000000,
|
|
35
|
-
ttlMs: 60000,
|
|
36
|
-
}
|
|
37
|
-
const body: BrowserEnvelopeBody = {
|
|
38
|
-
intent: 'TASK',
|
|
39
|
-
content: 'route:alpha->beta',
|
|
40
|
-
capability: 'task.plan',
|
|
41
|
-
correlationId: 'corr-1',
|
|
42
|
-
}
|
|
43
|
-
const expected =
|
|
44
|
-
'{"body":{"capability":"task.plan","content":"route:alpha->beta","correlationId":"corr-1","intent":"TASK"},"header":{"messageId":"vec-1","nonce":"nonce-vec-1","recipient":"agent.beta","sender":"agent.alpha","timestampMs":1712500000000,"ttlMs":60000,"version":"7h3/0.1"}}'
|
|
45
|
-
expect(canonicalizeEnvelope({ header, body })).toBe(expected)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// Test 3: canonicalizeEnvelope with no optional fields
|
|
49
|
-
it('canonicalizeEnvelope omits undefined optional fields', () => {
|
|
50
|
-
const header: BrowserEnvelopeHeader = {
|
|
51
|
-
version: '7h3/0.1',
|
|
52
|
-
messageId: 'msg-1',
|
|
53
|
-
nonce: 'n1',
|
|
54
|
-
sender: 'agent.x',
|
|
55
|
-
timestampMs: 1712500000000,
|
|
56
|
-
ttlMs: 30000,
|
|
57
|
-
}
|
|
58
|
-
const body: BrowserEnvelopeBody = {
|
|
59
|
-
intent: 'PING',
|
|
60
|
-
content: 'hello',
|
|
61
|
-
}
|
|
62
|
-
const result = canonicalizeEnvelope({ header, body })
|
|
63
|
-
const parsed = JSON.parse(result)
|
|
64
|
-
expect(parsed.header.recipient).toBeUndefined()
|
|
65
|
-
expect(parsed.body.capability).toBeUndefined()
|
|
66
|
-
expect(parsed.body.correlationId).toBeUndefined()
|
|
67
|
-
// Verify the raw string doesn't include those keys
|
|
68
|
-
expect(result).not.toContain('"recipient"')
|
|
69
|
-
expect(result).not.toContain('"capability"')
|
|
70
|
-
expect(result).not.toContain('"correlationId"')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// Test 4: createEnvelope produces correct structure
|
|
74
|
-
it('createEnvelope produces correct structure', () => {
|
|
75
|
-
const env = createEnvelope({
|
|
76
|
-
sender: 'agent.test',
|
|
77
|
-
body: { intent: 'TASK', content: 'do work' },
|
|
78
|
-
})
|
|
79
|
-
expect(env.header.version).toBe(WIRE_VERSION)
|
|
80
|
-
expect(typeof env.header.messageId).toBe('string')
|
|
81
|
-
expect(env.header.messageId.length).toBeGreaterThan(0)
|
|
82
|
-
expect(env.body.intent).toBe('TASK')
|
|
83
|
-
expect(env.header.sender).toBe('agent.test')
|
|
84
|
-
expect(env.header.ttlMs).toBe(60_000)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
// Test 5: signEnvelope + verifyEnvelope round trip
|
|
88
|
-
it('signEnvelope + verifyEnvelope round trip', async () => {
|
|
89
|
-
const kp = await generateKeypair()
|
|
90
|
-
const env = createEnvelope({
|
|
91
|
-
sender: 'agent.a',
|
|
92
|
-
body: { intent: 'PING', content: 'test' },
|
|
93
|
-
})
|
|
94
|
-
const signed = await signEnvelope(env, kp.privateKey)
|
|
95
|
-
expect(signed.signature).toBeDefined()
|
|
96
|
-
expect(signed.signature?.alg).toBe('ED25519')
|
|
97
|
-
const valid = await verifyEnvelope(signed, kp.publicKey)
|
|
98
|
-
expect(valid).toBe(true)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Test 6: verifyEnvelope: tampered body returns false
|
|
102
|
-
it('verifyEnvelope: tampered body returns false', async () => {
|
|
103
|
-
const kp = await generateKeypair()
|
|
104
|
-
const env = createEnvelope({
|
|
105
|
-
sender: 'agent.a',
|
|
106
|
-
body: { intent: 'PING', content: 'original' },
|
|
107
|
-
})
|
|
108
|
-
const signed = await signEnvelope(env, kp.privateKey)
|
|
109
|
-
const tampered = {
|
|
110
|
-
...signed,
|
|
111
|
-
body: { ...signed.body, content: 'tampered' },
|
|
112
|
-
}
|
|
113
|
-
const valid = await verifyEnvelope(tampered, kp.publicKey)
|
|
114
|
-
expect(valid).toBe(false)
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
// Test 7: verifyEnvelope: wrong key returns false
|
|
118
|
-
it('verifyEnvelope: wrong key returns false', async () => {
|
|
119
|
-
const kp1 = await generateKeypair()
|
|
120
|
-
const kp2 = await generateKeypair()
|
|
121
|
-
const env = createEnvelope({
|
|
122
|
-
sender: 'agent.a',
|
|
123
|
-
body: { intent: 'PING', content: 'test' },
|
|
124
|
-
})
|
|
125
|
-
const signed = await signEnvelope(env, kp1.privateKey)
|
|
126
|
-
const valid = await verifyEnvelope(signed, kp2.publicKey)
|
|
127
|
-
expect(valid).toBe(false)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
// Test 8: signRequest: produces Request with x-7h3-envelope header set
|
|
131
|
-
it('signRequest: produces Request with x-7h3-envelope header', async () => {
|
|
132
|
-
const kp = await generateKeypair()
|
|
133
|
-
const req = new Request('https://example.com/api')
|
|
134
|
-
const signed = await signRequest(req, {
|
|
135
|
-
sender: 'agent.client',
|
|
136
|
-
privateKey: kp.privateKey,
|
|
137
|
-
})
|
|
138
|
-
const headerVal = signed.headers.get(ENVELOPE_HEADER)
|
|
139
|
-
expect(headerVal).not.toBeNull()
|
|
140
|
-
expect(typeof headerVal).toBe('string')
|
|
141
|
-
const parsed = JSON.parse(headerVal!)
|
|
142
|
-
expect(parsed.signature).toBeDefined()
|
|
143
|
-
expect(parsed.header.sender).toBe('agent.client')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// Test 9: isEnvelopeExpired: returns true for past timestamp
|
|
147
|
-
it('isEnvelopeExpired: returns true for past timestamp', () => {
|
|
148
|
-
const past = Date.now() - 120_000
|
|
149
|
-
const env = {
|
|
150
|
-
header: {
|
|
151
|
-
version: '7h3/0.1' as const,
|
|
152
|
-
messageId: 'x',
|
|
153
|
-
nonce: 'y',
|
|
154
|
-
sender: 'agent.z',
|
|
155
|
-
timestampMs: past,
|
|
156
|
-
ttlMs: 60_000,
|
|
157
|
-
},
|
|
158
|
-
body: { intent: 'PING' as const, content: 'old' },
|
|
159
|
-
}
|
|
160
|
-
expect(isEnvelopeExpired(env)).toBe(true)
|
|
161
|
-
})
|
|
162
|
-
})
|
package/sdk/browser/index.ts
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
// 7h3 Protocol - Browser/Edge SDK
|
|
2
|
-
// Pure Web Crypto API. Zero dependencies.
|
|
3
|
-
// Works in browsers, Deno, Cloudflare Workers, Bun, and Node 20+.
|
|
4
|
-
|
|
5
|
-
export const WIRE_VERSION = '7h3/0.1'
|
|
6
|
-
export const ENVELOPE_HEADER = 'x-7h3-envelope'
|
|
7
|
-
export const RESPONSE_HEADER = 'x-7h3-response'
|
|
8
|
-
|
|
9
|
-
export type IntentKind = 'PING' | 'PONG' | 'CAPS' | 'TASK' | 'RESULT' | 'ERROR'
|
|
10
|
-
|
|
11
|
-
export interface BrowserEnvelopeHeader {
|
|
12
|
-
version: typeof WIRE_VERSION
|
|
13
|
-
messageId: string
|
|
14
|
-
timestampMs: number
|
|
15
|
-
ttlMs: number
|
|
16
|
-
sender: string
|
|
17
|
-
recipient?: string
|
|
18
|
-
nonce: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface BrowserEnvelopeBody {
|
|
22
|
-
intent: IntentKind
|
|
23
|
-
content: string
|
|
24
|
-
capability?: string
|
|
25
|
-
correlationId?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface BrowserSignature {
|
|
29
|
-
alg: 'ED25519'
|
|
30
|
-
keyId: string
|
|
31
|
-
value: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface BrowserEnvelope {
|
|
35
|
-
header: BrowserEnvelopeHeader
|
|
36
|
-
body: BrowserEnvelopeBody
|
|
37
|
-
signature?: BrowserSignature
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface BrowserKeyPair {
|
|
41
|
-
publicKey: string // base64url SPKI, no padding
|
|
42
|
-
privateKey: string // base64url PKCS8, no padding
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ─── Internal helpers (NOT exported) ─────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
function toBase64Url(buf: ArrayBuffer | Uint8Array): string {
|
|
48
|
-
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
|
49
|
-
let binary = ''
|
|
50
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
51
|
-
binary += String.fromCharCode(bytes[i])
|
|
52
|
-
}
|
|
53
|
-
return btoa(binary)
|
|
54
|
-
.replace(/\+/g, '-')
|
|
55
|
-
.replace(/\//g, '_')
|
|
56
|
-
.replace(/=+$/g, '')
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fromBase64Url(str: string): Uint8Array {
|
|
60
|
-
const padded = str
|
|
61
|
-
.replace(/-/g, '+')
|
|
62
|
-
.replace(/_/g, '/')
|
|
63
|
-
.padEnd(Math.ceil(str.length / 4) * 4, '=')
|
|
64
|
-
const binary = atob(padded)
|
|
65
|
-
const bytes = new Uint8Array(binary.length)
|
|
66
|
-
for (let i = 0; i < binary.length; i++) {
|
|
67
|
-
bytes[i] = binary.charCodeAt(i)
|
|
68
|
-
}
|
|
69
|
-
return bytes
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function generateNonce(): string {
|
|
73
|
-
const bytes = new Uint8Array(16)
|
|
74
|
-
crypto.getRandomValues(bytes)
|
|
75
|
-
return toBase64Url(bytes)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function canonicalBody(b: BrowserEnvelopeBody): string {
|
|
79
|
-
const parts: string[] = []
|
|
80
|
-
if (b.capability !== undefined) {
|
|
81
|
-
parts.push(`"capability":${JSON.stringify(b.capability)}`)
|
|
82
|
-
}
|
|
83
|
-
parts.push(`"content":${JSON.stringify(b.content)}`)
|
|
84
|
-
if (b.correlationId !== undefined) {
|
|
85
|
-
parts.push(`"correlationId":${JSON.stringify(b.correlationId)}`)
|
|
86
|
-
}
|
|
87
|
-
parts.push(`"intent":${JSON.stringify(b.intent)}`)
|
|
88
|
-
return `{${parts.join(',')}}`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function canonicalHeader(h: BrowserEnvelopeHeader): string {
|
|
92
|
-
const parts: string[] = [
|
|
93
|
-
`"messageId":${JSON.stringify(h.messageId)}`,
|
|
94
|
-
`"nonce":${JSON.stringify(h.nonce)}`,
|
|
95
|
-
]
|
|
96
|
-
if (h.recipient !== undefined) {
|
|
97
|
-
parts.push(`"recipient":${JSON.stringify(h.recipient)}`)
|
|
98
|
-
}
|
|
99
|
-
parts.push(`"sender":${JSON.stringify(h.sender)}`)
|
|
100
|
-
parts.push(`"timestampMs":${h.timestampMs}`)
|
|
101
|
-
parts.push(`"ttlMs":${h.ttlMs}`)
|
|
102
|
-
parts.push(`"version":${JSON.stringify(h.version)}`)
|
|
103
|
-
return `{${parts.join(',')}}`
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
export function canonicalizeEnvelope(envelope: Omit<BrowserEnvelope, 'signature'>): string {
|
|
109
|
-
return `{"body":${canonicalBody(envelope.body)},"header":${canonicalHeader(envelope.header)}}`
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export async function generateKeypair(): Promise<BrowserKeyPair> {
|
|
113
|
-
const pair = await crypto.subtle.generateKey(
|
|
114
|
-
{ name: 'Ed25519' } as AlgorithmIdentifier,
|
|
115
|
-
true,
|
|
116
|
-
['sign', 'verify'],
|
|
117
|
-
) as CryptoKeyPair
|
|
118
|
-
|
|
119
|
-
const privateKeyRaw = await crypto.subtle.exportKey('pkcs8', pair.privateKey)
|
|
120
|
-
const publicKeyRaw = await crypto.subtle.exportKey('spki', pair.publicKey)
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
publicKey: toBase64Url(publicKeyRaw),
|
|
124
|
-
privateKey: toBase64Url(privateKeyRaw),
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export async function signEnvelope(
|
|
129
|
-
envelope: Omit<BrowserEnvelope, 'signature'>,
|
|
130
|
-
privateKeyPkcs8Base64Url: string,
|
|
131
|
-
keyId?: string,
|
|
132
|
-
): Promise<BrowserEnvelope> {
|
|
133
|
-
const der = fromBase64Url(privateKeyPkcs8Base64Url)
|
|
134
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
135
|
-
'pkcs8',
|
|
136
|
-
der.buffer.slice(der.byteOffset, der.byteOffset + der.byteLength) as ArrayBuffer,
|
|
137
|
-
{ name: 'Ed25519' } as AlgorithmIdentifier,
|
|
138
|
-
false,
|
|
139
|
-
['sign'],
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
const payload = canonicalizeEnvelope(envelope)
|
|
143
|
-
const sigBuffer = await crypto.subtle.sign(
|
|
144
|
-
'Ed25519',
|
|
145
|
-
cryptoKey,
|
|
146
|
-
new TextEncoder().encode(payload),
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
...envelope,
|
|
151
|
-
signature: {
|
|
152
|
-
alg: 'ED25519',
|
|
153
|
-
keyId: keyId ?? generateNonce().slice(0, 16),
|
|
154
|
-
value: toBase64Url(sigBuffer),
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function verifyEnvelope(
|
|
160
|
-
envelope: BrowserEnvelope,
|
|
161
|
-
publicKeySpkiBase64Url: string,
|
|
162
|
-
): Promise<boolean> {
|
|
163
|
-
if (!envelope.signature) return false
|
|
164
|
-
if (envelope.signature.alg !== 'ED25519') return false
|
|
165
|
-
|
|
166
|
-
const der = fromBase64Url(publicKeySpkiBase64Url)
|
|
167
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
168
|
-
'spki',
|
|
169
|
-
der.buffer.slice(der.byteOffset, der.byteOffset + der.byteLength) as ArrayBuffer,
|
|
170
|
-
{ name: 'Ed25519' } as AlgorithmIdentifier,
|
|
171
|
-
false,
|
|
172
|
-
['verify'],
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
const unsigned: Omit<BrowserEnvelope, 'signature'> = {
|
|
176
|
-
header: envelope.header,
|
|
177
|
-
body: envelope.body,
|
|
178
|
-
}
|
|
179
|
-
const payload = canonicalizeEnvelope(unsigned)
|
|
180
|
-
const sigBytes = fromBase64Url(envelope.signature.value)
|
|
181
|
-
|
|
182
|
-
return crypto.subtle.verify(
|
|
183
|
-
'Ed25519',
|
|
184
|
-
cryptoKey,
|
|
185
|
-
sigBytes.buffer.slice(sigBytes.byteOffset, sigBytes.byteOffset + sigBytes.byteLength) as ArrayBuffer,
|
|
186
|
-
new TextEncoder().encode(payload),
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function createEnvelope(opts: {
|
|
191
|
-
sender: string
|
|
192
|
-
recipient?: string
|
|
193
|
-
ttlMs?: number
|
|
194
|
-
body: BrowserEnvelopeBody
|
|
195
|
-
}): Omit<BrowserEnvelope, 'signature'> {
|
|
196
|
-
return {
|
|
197
|
-
header: {
|
|
198
|
-
version: WIRE_VERSION,
|
|
199
|
-
messageId: generateNonce(),
|
|
200
|
-
timestampMs: Date.now(),
|
|
201
|
-
ttlMs: opts.ttlMs ?? 60_000,
|
|
202
|
-
sender: opts.sender,
|
|
203
|
-
recipient: opts.recipient,
|
|
204
|
-
nonce: generateNonce(),
|
|
205
|
-
},
|
|
206
|
-
body: opts.body,
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export async function signRequest(
|
|
211
|
-
request: Request,
|
|
212
|
-
opts: {
|
|
213
|
-
sender: string
|
|
214
|
-
recipient?: string
|
|
215
|
-
ttlMs?: number
|
|
216
|
-
privateKey: string
|
|
217
|
-
keyId?: string
|
|
218
|
-
},
|
|
219
|
-
): Promise<Request> {
|
|
220
|
-
const envelope = createEnvelope({
|
|
221
|
-
sender: opts.sender,
|
|
222
|
-
recipient: opts.recipient,
|
|
223
|
-
ttlMs: opts.ttlMs,
|
|
224
|
-
body: {
|
|
225
|
-
intent: 'TASK',
|
|
226
|
-
content: request.url,
|
|
227
|
-
},
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
const signed = await signEnvelope(envelope, opts.privateKey, opts.keyId)
|
|
231
|
-
|
|
232
|
-
const headers = new Headers(request.headers)
|
|
233
|
-
headers.set(ENVELOPE_HEADER, JSON.stringify(signed))
|
|
234
|
-
|
|
235
|
-
return new Request(request, { headers })
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export async function verifyResponseHeader(
|
|
239
|
-
response: Response,
|
|
240
|
-
publicKey: string,
|
|
241
|
-
): Promise<boolean> {
|
|
242
|
-
const headerValue = response.headers.get(RESPONSE_HEADER)
|
|
243
|
-
if (!headerValue) return false
|
|
244
|
-
|
|
245
|
-
let parsed: BrowserEnvelope
|
|
246
|
-
try {
|
|
247
|
-
parsed = JSON.parse(headerValue) as BrowserEnvelope
|
|
248
|
-
} catch {
|
|
249
|
-
return false
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return verifyEnvelope(parsed, publicKey)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export function isEnvelopeExpired(envelope: BrowserEnvelope): boolean {
|
|
256
|
-
return envelope.header.timestampMs + envelope.header.ttlMs < Date.now()
|
|
257
|
-
}
|
package/sdk/browser/package.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@7h3/protocol-browser",
|
|
3
|
-
"version": "0.4.0",
|
|
4
|
-
"description": "7h3 Protocol - Browser/Edge SDK. Pure Web Crypto API. Zero dependencies.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.js",
|
|
7
|
-
"module": "./index.js",
|
|
8
|
-
"types": "./index.d.ts",
|
|
9
|
-
"exports": { ".": { "types": "./index.d.ts", "import": "./index.js" } },
|
|
10
|
-
"keywords": ["7h3","protocol","browser","webcrypto","ed25519","signing","mcp"],
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"sideEffects": false
|
|
13
|
-
}
|
package/sdk/go/go.mod
DELETED