@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,76 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { performance } from 'node:perf_hooks'
|
|
3
|
-
import process from 'node:process'
|
|
4
|
-
import { createEnvelope, type ProtocolEnvelope } from '../src/protocol'
|
|
5
|
-
import { InMemoryReplayCache } from '../src/protocolReplay'
|
|
6
|
-
|
|
7
|
-
type ProfileName = 'quick' | 'full'
|
|
8
|
-
|
|
9
|
-
const PROFILES: Record<ProfileName, { iterations: number; batchSize: number }> = {
|
|
10
|
-
quick: { iterations: 50_000, batchSize: 32 },
|
|
11
|
-
full: { iterations: 500_000, batchSize: 128 },
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseProfile(argv: string[]): ProfileName {
|
|
15
|
-
const idx = argv.indexOf('--profile')
|
|
16
|
-
const value = idx >= 0 ? argv[idx + 1] : undefined
|
|
17
|
-
return value === 'full' ? 'full' : 'quick'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function buildEnvelope(sequence: number): ProtocolEnvelope {
|
|
21
|
-
return createEnvelope({
|
|
22
|
-
sender: 'bench.replay',
|
|
23
|
-
intent: 'PING',
|
|
24
|
-
content: 'replay-bench',
|
|
25
|
-
messageId: `replay-${sequence}`,
|
|
26
|
-
nonce: `nonce-${sequence}`,
|
|
27
|
-
nowMs: 1_700_000_000_000,
|
|
28
|
-
ttlMs: 60_000,
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function round3(value: number): number {
|
|
33
|
-
return Number(value.toFixed(3))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function run(): Promise<void> {
|
|
37
|
-
const profileName = parseProfile(process.argv)
|
|
38
|
-
const profile = PROFILES[profileName]
|
|
39
|
-
const cache = new InMemoryReplayCache(profile.iterations * 2)
|
|
40
|
-
const envelopes = Array.from({ length: profile.iterations }, (_, index) => buildEnvelope(index))
|
|
41
|
-
|
|
42
|
-
const singleStart = performance.now()
|
|
43
|
-
for (const envelope of envelopes) {
|
|
44
|
-
const result = cache.consume(envelope, 1_700_000_000_001)
|
|
45
|
-
if (!result.ok) throw new Error('single replay benchmark unexpectedly rejected envelope')
|
|
46
|
-
}
|
|
47
|
-
const singleMs = performance.now() - singleStart
|
|
48
|
-
|
|
49
|
-
const batchCache = new InMemoryReplayCache(profile.iterations * 2)
|
|
50
|
-
const batchStart = performance.now()
|
|
51
|
-
for (let offset = 0; offset < envelopes.length; offset += profile.batchSize) {
|
|
52
|
-
const results = batchCache.consumeMany(envelopes.slice(offset, offset + profile.batchSize), 1_700_000_000_001)
|
|
53
|
-
if (results.some((result) => !result.ok)) throw new Error('batch replay benchmark unexpectedly rejected envelope')
|
|
54
|
-
}
|
|
55
|
-
const batchMs = performance.now() - batchStart
|
|
56
|
-
|
|
57
|
-
const output = {
|
|
58
|
-
generatedAt: new Date().toISOString(),
|
|
59
|
-
profile: profileName,
|
|
60
|
-
iterations: profile.iterations,
|
|
61
|
-
batchSize: profile.batchSize,
|
|
62
|
-
singleOpsPerSecond: round3((profile.iterations * 1000) / singleMs),
|
|
63
|
-
batchOpsPerSecond: round3((profile.iterations * 1000) / batchMs),
|
|
64
|
-
singleUsPerOp: round3((singleMs * 1000) / profile.iterations),
|
|
65
|
-
batchUsPerOp: round3((batchMs * 1000) / profile.iterations),
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
console.log(JSON.stringify(output, null, 2))
|
|
69
|
-
await mkdir('bench-results', { recursive: true })
|
|
70
|
-
await writeFile(`bench-results/replay-cache-${profileName}-${Date.now()}.json`, `${JSON.stringify(output, null, 2)}\n`, 'utf8')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
run().catch((error: unknown) => {
|
|
74
|
-
console.error(error instanceof Error ? error.stack ?? error.message : String(error))
|
|
75
|
-
process.exitCode = 1
|
|
76
|
-
})
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { performance } from 'node:perf_hooks'
|
|
3
|
-
import process from 'node:process'
|
|
4
|
-
import {
|
|
5
|
-
canonicalizeEnvelope,
|
|
6
|
-
createEnvelope,
|
|
7
|
-
generateEd25519KeypairBase64Url,
|
|
8
|
-
signCanonicalPayloadEd25519,
|
|
9
|
-
signCanonicalPayloadHmac,
|
|
10
|
-
verifyCanonicalPayloadEd25519,
|
|
11
|
-
verifyCanonicalPayloadHmac,
|
|
12
|
-
} from '../src/protocol'
|
|
13
|
-
|
|
14
|
-
type ProfileName = 'quick' | 'full'
|
|
15
|
-
type SignatureAlg = 'HS256' | 'ED25519'
|
|
16
|
-
|
|
17
|
-
interface Profile {
|
|
18
|
-
iterations: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface ScenarioResult {
|
|
22
|
-
profile: ProfileName
|
|
23
|
-
payloadBytes: number
|
|
24
|
-
algorithm: SignatureAlg
|
|
25
|
-
signUsPerOp: number
|
|
26
|
-
verifyUsPerOp: number
|
|
27
|
-
signOpsPerSecond: number
|
|
28
|
-
verifyOpsPerSecond: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const PROFILES: Record<ProfileName, Profile> = {
|
|
32
|
-
quick: { iterations: 2_000 },
|
|
33
|
-
full: { iterations: 20_000 },
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const PAYLOAD_SIZES = [256, 1024, 4096, 16384]
|
|
37
|
-
|
|
38
|
-
function parseProfile(argv: string[]): ProfileName {
|
|
39
|
-
const idx = argv.indexOf('--profile')
|
|
40
|
-
if (idx >= 0) {
|
|
41
|
-
const value = argv[idx + 1]
|
|
42
|
-
if (value === 'quick' || value === 'full') return value
|
|
43
|
-
}
|
|
44
|
-
return 'quick'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function payloadOfSize(bytes: number): string {
|
|
48
|
-
if (bytes <= 0) return ''
|
|
49
|
-
return 'x'.repeat(bytes)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function round3(value: number): number {
|
|
53
|
-
return Number(value.toFixed(3))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function measureSignVerify(
|
|
57
|
-
algorithm: SignatureAlg,
|
|
58
|
-
canonical: string,
|
|
59
|
-
iterations: number,
|
|
60
|
-
material: { secret: string } | { privateKey: string; publicKey: string },
|
|
61
|
-
): Promise<Pick<ScenarioResult, 'signUsPerOp' | 'verifyUsPerOp' | 'signOpsPerSecond' | 'verifyOpsPerSecond'>> {
|
|
62
|
-
const signStart = performance.now()
|
|
63
|
-
let lastSignature = ''
|
|
64
|
-
for (let i = 0; i < iterations; i += 1) {
|
|
65
|
-
if (algorithm === 'HS256') {
|
|
66
|
-
lastSignature = await signCanonicalPayloadHmac(canonical, material.secret)
|
|
67
|
-
} else {
|
|
68
|
-
lastSignature = await signCanonicalPayloadEd25519(canonical, material.privateKey)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const signMs = performance.now() - signStart
|
|
72
|
-
|
|
73
|
-
const verifyStart = performance.now()
|
|
74
|
-
let verifiedCount = 0
|
|
75
|
-
for (let i = 0; i < iterations; i += 1) {
|
|
76
|
-
const ok =
|
|
77
|
-
algorithm === 'HS256'
|
|
78
|
-
? await verifyCanonicalPayloadHmac(canonical, lastSignature, material.secret)
|
|
79
|
-
: await verifyCanonicalPayloadEd25519(canonical, lastSignature, material.publicKey)
|
|
80
|
-
if (ok) verifiedCount += 1
|
|
81
|
-
}
|
|
82
|
-
const verifyMs = performance.now() - verifyStart
|
|
83
|
-
if (verifiedCount !== iterations) {
|
|
84
|
-
throw new Error(`Verification mismatch for ${algorithm}: ${verifiedCount}/${iterations}`)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
signUsPerOp: round3((signMs * 1000) / iterations),
|
|
89
|
-
verifyUsPerOp: round3((verifyMs * 1000) / iterations),
|
|
90
|
-
signOpsPerSecond: round3((iterations * 1000) / signMs),
|
|
91
|
-
verifyOpsPerSecond: round3((iterations * 1000) / verifyMs),
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function printResults(results: ScenarioResult[]): void {
|
|
96
|
-
console.log('\nAIP signature profile benchmark results\n')
|
|
97
|
-
for (const result of results) {
|
|
98
|
-
console.log(
|
|
99
|
-
[
|
|
100
|
-
`profile=${result.profile}`,
|
|
101
|
-
`payload=${result.payloadBytes}B`,
|
|
102
|
-
`alg=${result.algorithm}`,
|
|
103
|
-
`sign_us_op=${result.signUsPerOp}`,
|
|
104
|
-
`verify_us_op=${result.verifyUsPerOp}`,
|
|
105
|
-
`sign_ops_s=${result.signOpsPerSecond}`,
|
|
106
|
-
`verify_ops_s=${result.verifyOpsPerSecond}`,
|
|
107
|
-
].join(' | '),
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function run(): Promise<void> {
|
|
113
|
-
const profileName = parseProfile(process.argv)
|
|
114
|
-
const profile = PROFILES[profileName]
|
|
115
|
-
const sharedSecret = 'bench-signature-secret'
|
|
116
|
-
const ed25519Keys = await generateEd25519KeypairBase64Url()
|
|
117
|
-
|
|
118
|
-
const results: ScenarioResult[] = []
|
|
119
|
-
|
|
120
|
-
for (const payloadBytes of PAYLOAD_SIZES) {
|
|
121
|
-
const unsigned = createEnvelope({
|
|
122
|
-
sender: 'agent.bench',
|
|
123
|
-
recipient: 'agent.worker',
|
|
124
|
-
intent: 'TASK',
|
|
125
|
-
content: payloadOfSize(payloadBytes),
|
|
126
|
-
capability: 'task.bench',
|
|
127
|
-
correlationId: `bench-${payloadBytes}`,
|
|
128
|
-
messageId: `bench-${payloadBytes}`,
|
|
129
|
-
nonce: `n-${payloadBytes}`,
|
|
130
|
-
nowMs: 1_700_000_000_000,
|
|
131
|
-
ttlMs: 60_000,
|
|
132
|
-
})
|
|
133
|
-
const canonical = canonicalizeEnvelope(unsigned)
|
|
134
|
-
|
|
135
|
-
const hs = await measureSignVerify('HS256', canonical, profile.iterations, { secret: sharedSecret })
|
|
136
|
-
results.push({
|
|
137
|
-
profile: profileName,
|
|
138
|
-
payloadBytes,
|
|
139
|
-
algorithm: 'HS256',
|
|
140
|
-
...hs,
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const ed = await measureSignVerify('ED25519', canonical, profile.iterations, {
|
|
144
|
-
privateKey: ed25519Keys.privateKey,
|
|
145
|
-
publicKey: ed25519Keys.publicKey,
|
|
146
|
-
})
|
|
147
|
-
results.push({
|
|
148
|
-
profile: profileName,
|
|
149
|
-
payloadBytes,
|
|
150
|
-
algorithm: 'ED25519',
|
|
151
|
-
...ed,
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
printResults(results)
|
|
156
|
-
await mkdir('bench-results', { recursive: true })
|
|
157
|
-
const outputPath = `bench-results/signature-profiles-${profileName}-${Date.now()}.json`
|
|
158
|
-
await writeFile(
|
|
159
|
-
outputPath,
|
|
160
|
-
`${JSON.stringify(
|
|
161
|
-
{
|
|
162
|
-
generatedAt: new Date().toISOString(),
|
|
163
|
-
profile: profileName,
|
|
164
|
-
iterations: profile.iterations,
|
|
165
|
-
payloadSizes: PAYLOAD_SIZES,
|
|
166
|
-
results,
|
|
167
|
-
},
|
|
168
|
-
null,
|
|
169
|
-
2,
|
|
170
|
-
)}\n`,
|
|
171
|
-
'utf8',
|
|
172
|
-
)
|
|
173
|
-
console.log(`\nSaved benchmark results to ${outputPath}`)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
run().catch((error: unknown) => {
|
|
177
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
178
|
-
console.error(message)
|
|
179
|
-
process.exitCode = 1
|
|
180
|
-
})
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { performance } from 'node:perf_hooks'
|
|
3
|
-
import process from 'node:process'
|
|
4
|
-
import { createEnvelope, signEnvelopeHmac } from '../src/protocol'
|
|
5
|
-
import { decodeEnvelope, encodeEnvelope } from '../src/protocolTransport'
|
|
6
|
-
import { decodeEnvelopeBinary, encodeEnvelopeBinary } from '../src/protocolBinary'
|
|
7
|
-
|
|
8
|
-
type ProfileName = 'quick' | 'full'
|
|
9
|
-
|
|
10
|
-
interface Profile {
|
|
11
|
-
iterations: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface CodecResult {
|
|
15
|
-
profile: ProfileName
|
|
16
|
-
payloadBytes: number
|
|
17
|
-
format: 'compact-json' | 'binary'
|
|
18
|
-
encodedBytes: number
|
|
19
|
-
encodeUsPerOp: number
|
|
20
|
-
decodeUsPerOp: number
|
|
21
|
-
encodeOpsPerSecond: number
|
|
22
|
-
decodeOpsPerSecond: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const PROFILES: Record<ProfileName, Profile> = {
|
|
26
|
-
quick: { iterations: 10_000 },
|
|
27
|
-
full: { iterations: 100_000 },
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const PAYLOAD_SIZES = [256, 1024, 4096, 16384]
|
|
31
|
-
|
|
32
|
-
function parseProfile(argv: string[]): ProfileName {
|
|
33
|
-
const idx = argv.indexOf('--profile')
|
|
34
|
-
if (idx >= 0) {
|
|
35
|
-
const value = argv[idx + 1]
|
|
36
|
-
if (value === 'quick' || value === 'full') return value
|
|
37
|
-
}
|
|
38
|
-
return 'quick'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function payloadOfSize(bytes: number): string {
|
|
42
|
-
return 'x'.repeat(bytes)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function round3(value: number): number {
|
|
46
|
-
return Number(value.toFixed(3))
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function run(): Promise<void> {
|
|
50
|
-
const profileName = parseProfile(process.argv)
|
|
51
|
-
const profile = PROFILES[profileName]
|
|
52
|
-
|
|
53
|
-
const results: CodecResult[] = []
|
|
54
|
-
|
|
55
|
-
for (const payloadBytes of PAYLOAD_SIZES) {
|
|
56
|
-
const envelope = await signEnvelopeHmac(
|
|
57
|
-
createEnvelope({
|
|
58
|
-
sender: 'agent.bench',
|
|
59
|
-
recipient: 'agent.verify',
|
|
60
|
-
intent: 'TASK',
|
|
61
|
-
content: payloadOfSize(payloadBytes),
|
|
62
|
-
capability: 'task.bench',
|
|
63
|
-
correlationId: `corr-${payloadBytes}`,
|
|
64
|
-
messageId: `m-${payloadBytes}`,
|
|
65
|
-
nonce: `n-${payloadBytes}`,
|
|
66
|
-
nowMs: 1_700_000_000_000,
|
|
67
|
-
ttlMs: 60_000,
|
|
68
|
-
}),
|
|
69
|
-
'bench-secret',
|
|
70
|
-
'k1',
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
const compactEncoded = encodeEnvelope(envelope, 'compact')
|
|
74
|
-
const compactSize = Buffer.byteLength(compactEncoded, 'utf8')
|
|
75
|
-
const compactEncodeStart = performance.now()
|
|
76
|
-
for (let i = 0; i < profile.iterations; i += 1) {
|
|
77
|
-
encodeEnvelope(envelope, 'compact')
|
|
78
|
-
}
|
|
79
|
-
const compactEncodeMs = performance.now() - compactEncodeStart
|
|
80
|
-
|
|
81
|
-
const compactDecodeStart = performance.now()
|
|
82
|
-
for (let i = 0; i < profile.iterations; i += 1) {
|
|
83
|
-
const decoded = decodeEnvelope(compactEncoded)
|
|
84
|
-
if (!decoded.ok) throw new Error('Compact decode failed')
|
|
85
|
-
}
|
|
86
|
-
const compactDecodeMs = performance.now() - compactDecodeStart
|
|
87
|
-
|
|
88
|
-
results.push({
|
|
89
|
-
profile: profileName,
|
|
90
|
-
payloadBytes,
|
|
91
|
-
format: 'compact-json',
|
|
92
|
-
encodedBytes: compactSize,
|
|
93
|
-
encodeUsPerOp: round3((compactEncodeMs * 1000) / profile.iterations),
|
|
94
|
-
decodeUsPerOp: round3((compactDecodeMs * 1000) / profile.iterations),
|
|
95
|
-
encodeOpsPerSecond: round3((profile.iterations * 1000) / compactEncodeMs),
|
|
96
|
-
decodeOpsPerSecond: round3((profile.iterations * 1000) / compactDecodeMs),
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
const binaryEncoded = encodeEnvelopeBinary(envelope)
|
|
100
|
-
const binaryEncodeStart = performance.now()
|
|
101
|
-
for (let i = 0; i < profile.iterations; i += 1) {
|
|
102
|
-
encodeEnvelopeBinary(envelope)
|
|
103
|
-
}
|
|
104
|
-
const binaryEncodeMs = performance.now() - binaryEncodeStart
|
|
105
|
-
|
|
106
|
-
const binaryDecodeStart = performance.now()
|
|
107
|
-
for (let i = 0; i < profile.iterations; i += 1) {
|
|
108
|
-
const decoded = decodeEnvelopeBinary(binaryEncoded)
|
|
109
|
-
if (!decoded.ok) throw new Error('Binary decode failed')
|
|
110
|
-
}
|
|
111
|
-
const binaryDecodeMs = performance.now() - binaryDecodeStart
|
|
112
|
-
|
|
113
|
-
results.push({
|
|
114
|
-
profile: profileName,
|
|
115
|
-
payloadBytes,
|
|
116
|
-
format: 'binary',
|
|
117
|
-
encodedBytes: binaryEncoded.byteLength,
|
|
118
|
-
encodeUsPerOp: round3((binaryEncodeMs * 1000) / profile.iterations),
|
|
119
|
-
decodeUsPerOp: round3((binaryDecodeMs * 1000) / profile.iterations),
|
|
120
|
-
encodeOpsPerSecond: round3((profile.iterations * 1000) / binaryEncodeMs),
|
|
121
|
-
decodeOpsPerSecond: round3((profile.iterations * 1000) / binaryDecodeMs),
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log('\nWire codec benchmark results\n')
|
|
126
|
-
for (const row of results) {
|
|
127
|
-
console.log(
|
|
128
|
-
[
|
|
129
|
-
`profile=${row.profile}`,
|
|
130
|
-
`payload=${row.payloadBytes}B`,
|
|
131
|
-
`format=${row.format}`,
|
|
132
|
-
`size=${row.encodedBytes}B`,
|
|
133
|
-
`enc_us=${row.encodeUsPerOp}`,
|
|
134
|
-
`dec_us=${row.decodeUsPerOp}`,
|
|
135
|
-
`enc_ops_s=${row.encodeOpsPerSecond}`,
|
|
136
|
-
`dec_ops_s=${row.decodeOpsPerSecond}`,
|
|
137
|
-
].join(' | '),
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
await mkdir('bench-results', { recursive: true })
|
|
142
|
-
const outputPath = `bench-results/wire-codecs-${profileName}-${Date.now()}.json`
|
|
143
|
-
await writeFile(
|
|
144
|
-
outputPath,
|
|
145
|
-
`${JSON.stringify({
|
|
146
|
-
generatedAt: new Date().toISOString(),
|
|
147
|
-
profile: profileName,
|
|
148
|
-
iterations: profile.iterations,
|
|
149
|
-
payloadSizes: PAYLOAD_SIZES,
|
|
150
|
-
results,
|
|
151
|
-
}, null, 2)}\n`,
|
|
152
|
-
'utf8',
|
|
153
|
-
)
|
|
154
|
-
console.log(`\nSaved benchmark results to ${outputPath}`)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
run().catch((error: unknown) => {
|
|
158
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
159
|
-
console.error(message)
|
|
160
|
-
process.exitCode = 1
|
|
161
|
-
})
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { signEnvelopeEd25519, signEnvelopeHmac } from '../src/protocol'
|
|
3
|
-
import { encodeEnvelopeBinary } from '../src/protocolBinary'
|
|
4
|
-
import { AIP_V01_CONFORMANCE_VECTORS, AIP_V01_ED25519_CONFORMANCE_VECTORS } from '../src/conformanceVectors'
|
|
5
|
-
|
|
6
|
-
function toHex(bytes: Uint8Array): string {
|
|
7
|
-
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function run(): Promise<void> {
|
|
11
|
-
const hmac = await Promise.all(
|
|
12
|
-
AIP_V01_CONFORMANCE_VECTORS.map(async (vector) => {
|
|
13
|
-
const signed = await signEnvelopeHmac(vector.envelope, vector.secret, vector.keyId)
|
|
14
|
-
return { id: vector.id, alg: 'HS256', binaryHex: toHex(encodeEnvelopeBinary(signed)) }
|
|
15
|
-
}),
|
|
16
|
-
)
|
|
17
|
-
const ed25519 = await Promise.all(
|
|
18
|
-
AIP_V01_ED25519_CONFORMANCE_VECTORS.map(async (vector) => {
|
|
19
|
-
const signed = await signEnvelopeEd25519(vector.envelope, vector.privateKey, vector.keyId)
|
|
20
|
-
return { id: vector.id, alg: 'ED25519', binaryHex: toHex(encodeEnvelopeBinary(signed)) }
|
|
21
|
-
}),
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
await mkdir('conformance', { recursive: true })
|
|
25
|
-
await writeFile(
|
|
26
|
-
'conformance/7h3_v0_1_binary.json',
|
|
27
|
-
`${JSON.stringify({ generatedAt: new Date().toISOString(), vectors: [...hmac, ...ed25519] }, null, 2)}\n`,
|
|
28
|
-
'utf8',
|
|
29
|
-
)
|
|
30
|
-
console.log('Wrote conformance/7h3_v0_1_binary.json')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
run().catch((error: unknown) => {
|
|
34
|
-
console.error(error instanceof Error ? error.stack ?? error.message : String(error))
|
|
35
|
-
process.exitCode = 1
|
|
36
|
-
})
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises'
|
|
2
|
-
import process from 'node:process'
|
|
3
|
-
|
|
4
|
-
interface E2ERow {
|
|
5
|
-
mode: string
|
|
6
|
-
payloadBytes: number
|
|
7
|
-
concurrency: number
|
|
8
|
-
opsPerSecond: number
|
|
9
|
-
p99Ms: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface OpenLoopRow {
|
|
13
|
-
mode: string
|
|
14
|
-
payloadBytes: number
|
|
15
|
-
concurrency: number
|
|
16
|
-
opsPerSecond: number
|
|
17
|
-
p99Ms: number
|
|
18
|
-
dropPct: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface SignatureRow {
|
|
22
|
-
algorithm: 'HS256' | 'ED25519'
|
|
23
|
-
signUsPerOp: number
|
|
24
|
-
verifyUsPerOp: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface WireRow {
|
|
28
|
-
payloadBytes: number
|
|
29
|
-
format: 'compact-json' | 'binary'
|
|
30
|
-
encodedBytes: number
|
|
31
|
-
encodeUsPerOp: number
|
|
32
|
-
decodeUsPerOp: number
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function readJson<T>(filePath: string): Promise<T> {
|
|
36
|
-
const raw = await readFile(filePath, 'utf8')
|
|
37
|
-
return JSON.parse(raw) as T
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function readJsonIfExists<T>(filePath: string): Promise<T | null> {
|
|
41
|
-
try {
|
|
42
|
-
await access(filePath)
|
|
43
|
-
return await readJson<T>(filePath)
|
|
44
|
-
} catch {
|
|
45
|
-
return null
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function newestFileMatching(dir: string, prefix: string): Promise<string | null> {
|
|
50
|
-
let entries: string[]
|
|
51
|
-
try {
|
|
52
|
-
entries = await readdir(dir)
|
|
53
|
-
} catch {
|
|
54
|
-
return null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const matches = entries.filter((name) => name.startsWith(prefix) && name.endsWith('.json'))
|
|
58
|
-
if (matches.length === 0) return null
|
|
59
|
-
|
|
60
|
-
let newest: { path: string; mtimeMs: number } | null = null
|
|
61
|
-
for (const name of matches) {
|
|
62
|
-
const path = `${dir}/${name}`
|
|
63
|
-
const info = await stat(path)
|
|
64
|
-
if (!newest || info.mtimeMs > newest.mtimeMs) {
|
|
65
|
-
newest = { path, mtimeMs: info.mtimeMs }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return newest?.path ?? null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function range(values: number[]): { min: number; max: number } {
|
|
72
|
-
if (values.length === 0) return { min: 0, max: 0 }
|
|
73
|
-
return {
|
|
74
|
-
min: Number(Math.min(...values).toFixed(3)),
|
|
75
|
-
max: Number(Math.max(...values).toFixed(3)),
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function main(): Promise<void> {
|
|
80
|
-
const warnings: string[] = []
|
|
81
|
-
|
|
82
|
-
const e2e = await readJsonIfExists<{ results: E2ERow[] }>('dist/bench/protocol-e2e.quick.latest.json')
|
|
83
|
-
const openloop = await readJsonIfExists<{ results: OpenLoopRow[] }>('dist/bench/protocol-openloop.quick.latest.json')
|
|
84
|
-
if (!e2e) warnings.push('Missing dist/bench/protocol-e2e.quick.latest.json (run npm run bench:e2e:quick)')
|
|
85
|
-
if (!openloop) warnings.push('Missing dist/bench/protocol-openloop.quick.latest.json (run npm run bench:openloop:adaptive:quick)')
|
|
86
|
-
|
|
87
|
-
const signaturePath = await newestFileMatching('bench-results', 'signature-profiles-quick-')
|
|
88
|
-
const wirePath = await newestFileMatching('bench-results', 'wire-codecs-quick-')
|
|
89
|
-
|
|
90
|
-
const signature = signaturePath ? await readJson<{ results: SignatureRow[] }>(signaturePath) : { results: [] }
|
|
91
|
-
const wire = wirePath ? await readJson<{ results: WireRow[] }>(wirePath) : { results: [] }
|
|
92
|
-
|
|
93
|
-
const e2eRows = e2e?.results ?? []
|
|
94
|
-
const openloopRows = openloop?.results ?? []
|
|
95
|
-
|
|
96
|
-
const e2eHttp1000 = e2eRows.find((row) => row.mode === 'http' && row.payloadBytes === 256 && row.concurrency === 1000) ?? null
|
|
97
|
-
const e2eWs100 = e2eRows.find((row) => row.mode === 'ws' && row.payloadBytes === 256 && row.concurrency === 100) ?? null
|
|
98
|
-
const e2eInproc100 = e2eRows.find((row) => row.mode === 'inproc' && row.payloadBytes === 256 && row.concurrency === 100) ?? null
|
|
99
|
-
|
|
100
|
-
const openloopHttp1000 = openloopRows.find((row) => row.mode === 'http' && row.payloadBytes === 256 && row.concurrency === 1000) ?? null
|
|
101
|
-
|
|
102
|
-
const hs = signature.results.filter((row) => row.algorithm === 'HS256')
|
|
103
|
-
const ed = signature.results.filter((row) => row.algorithm === 'ED25519')
|
|
104
|
-
const hsSign = range(hs.map((row) => row.signUsPerOp))
|
|
105
|
-
const hsVerify = range(hs.map((row) => row.verifyUsPerOp))
|
|
106
|
-
const edSign = range(ed.map((row) => row.signUsPerOp))
|
|
107
|
-
const edVerify = range(ed.map((row) => row.verifyUsPerOp))
|
|
108
|
-
|
|
109
|
-
const wire256Compact =
|
|
110
|
-
wire.results.find((row) => row.payloadBytes === 256 && row.format === 'compact-json') ?? null
|
|
111
|
-
const wire256Binary = wire.results.find((row) => row.payloadBytes === 256 && row.format === 'binary') ?? null
|
|
112
|
-
|
|
113
|
-
const dashboard = {
|
|
114
|
-
generatedAt: new Date().toISOString(),
|
|
115
|
-
warnings,
|
|
116
|
-
highlights: {
|
|
117
|
-
e2eHttp1000,
|
|
118
|
-
e2eWs100,
|
|
119
|
-
e2eInproc100,
|
|
120
|
-
openloopHttp1000,
|
|
121
|
-
signatures: {
|
|
122
|
-
hs256: { signUsPerOp: hsSign, verifyUsPerOp: hsVerify },
|
|
123
|
-
ed25519: { signUsPerOp: edSign, verifyUsPerOp: edVerify },
|
|
124
|
-
},
|
|
125
|
-
wire256: {
|
|
126
|
-
compact: wire256Compact,
|
|
127
|
-
binary: wire256Binary,
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const markdownLines = [
|
|
133
|
-
'# Release Dashboard (Quick Bench)',
|
|
134
|
-
'',
|
|
135
|
-
`Generated: ${dashboard.generatedAt}`,
|
|
136
|
-
...(warnings.length > 0
|
|
137
|
-
? ['', '## Warnings', '', ...warnings.map((warning) => `- ${warning}`)]
|
|
138
|
-
: []),
|
|
139
|
-
'',
|
|
140
|
-
'## Transport highlights',
|
|
141
|
-
'',
|
|
142
|
-
`- E2E HTTP 256B c=1000: ops/s=${e2eHttp1000?.opsPerSecond ?? 0}, p99=${e2eHttp1000?.p99Ms ?? 0}ms`,
|
|
143
|
-
`- E2E WS 256B c=100: ops/s=${e2eWs100?.opsPerSecond ?? 0}, p99=${e2eWs100?.p99Ms ?? 0}ms`,
|
|
144
|
-
`- E2E InProc 256B c=100: ops/s=${e2eInproc100?.opsPerSecond ?? 0}, p99=${e2eInproc100?.p99Ms ?? 0}ms`,
|
|
145
|
-
`- OpenLoop HTTP 256B c=1000: ops/s=${openloopHttp1000?.opsPerSecond ?? 0}, p99=${openloopHttp1000?.p99Ms ?? 0}ms, drop=${openloopHttp1000?.dropPct ?? 0}%`,
|
|
146
|
-
'',
|
|
147
|
-
'## Signature ranges (us/op)',
|
|
148
|
-
'',
|
|
149
|
-
`- HS256 sign=${hsSign.min}-${hsSign.max}, verify=${hsVerify.min}-${hsVerify.max}`,
|
|
150
|
-
`- ED25519 sign=${edSign.min}-${edSign.max}, verify=${edVerify.min}-${edVerify.max}`,
|
|
151
|
-
'',
|
|
152
|
-
'## Wire format (256B)',
|
|
153
|
-
'',
|
|
154
|
-
`- compact-json size=${wire256Compact?.encodedBytes ?? 0}B, enc=${wire256Compact?.encodeUsPerOp ?? 0}us, dec=${wire256Compact?.decodeUsPerOp ?? 0}us`,
|
|
155
|
-
`- binary size=${wire256Binary?.encodedBytes ?? 0}B, enc=${wire256Binary?.encodeUsPerOp ?? 0}us, dec=${wire256Binary?.decodeUsPerOp ?? 0}us`,
|
|
156
|
-
'',
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
await mkdir('dist/release-dashboard', { recursive: true })
|
|
160
|
-
await writeFile('dist/release-dashboard/latest.json', `${JSON.stringify(dashboard, null, 2)}\n`, 'utf8')
|
|
161
|
-
await writeFile('dist/release-dashboard/latest.md', `${markdownLines.join('\n')}\n`, 'utf8')
|
|
162
|
-
|
|
163
|
-
console.log('Wrote dist/release-dashboard/latest.json')
|
|
164
|
-
console.log('Wrote dist/release-dashboard/latest.md')
|
|
165
|
-
if (warnings.length > 0) {
|
|
166
|
-
console.log('Dashboard generated with warnings:')
|
|
167
|
-
for (const warning of warnings) console.log(`- ${warning}`)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
main().catch((error: unknown) => {
|
|
172
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
173
|
-
console.error(message)
|
|
174
|
-
process.exitCode = 1
|
|
175
|
-
})
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
interface CanaryStage {
|
|
2
|
-
percent: number
|
|
3
|
-
durationMinutes: number
|
|
4
|
-
required: {
|
|
5
|
-
maxDropPct: number
|
|
6
|
-
maxP99Ms: number
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const DEFAULT_PLAN: CanaryStage[] = [
|
|
11
|
-
{ percent: 5, durationMinutes: 15, required: { maxDropPct: 0.1, maxP99Ms: 20 } },
|
|
12
|
-
{ percent: 25, durationMinutes: 30, required: { maxDropPct: 0.1, maxP99Ms: 20 } },
|
|
13
|
-
{ percent: 50, durationMinutes: 30, required: { maxDropPct: 0.1, maxP99Ms: 20 } },
|
|
14
|
-
{ percent: 100, durationMinutes: 60, required: { maxDropPct: 0.1, maxP99Ms: 20 } },
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
function renderStage(stage: CanaryStage): string {
|
|
18
|
-
return [
|
|
19
|
-
`- Stage ${stage.percent}% for ${stage.durationMinutes}m`,
|
|
20
|
-
` - maxDropPct <= ${stage.required.maxDropPct}`,
|
|
21
|
-
` - maxP99Ms <= ${stage.required.maxP99Ms}`,
|
|
22
|
-
].join('\n')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function main(): Promise<void> {
|
|
26
|
-
console.log('Canary rollout plan')
|
|
27
|
-
console.log('===================')
|
|
28
|
-
for (const stage of DEFAULT_PLAN) {
|
|
29
|
-
console.log(renderStage(stage))
|
|
30
|
-
}
|
|
31
|
-
console.log('\nRollback trigger: if any stage breaches limits for sustained window, rollback immediately.')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
main().catch((error: unknown) => {
|
|
35
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
36
|
-
console.error(message)
|
|
37
|
-
process.exitCode = 1
|
|
38
|
-
})
|