@7h3/protocol 0.1.2 → 0.4.0
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/.dockerignore +19 -0
- package/.github/dependabot.yml +32 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/publish.yml +59 -0
- package/.github/workflows/scorecard.yml +37 -0
- package/7h3.example.yaml +125 -0
- package/CHANGELOG.md +92 -0
- package/CONTRIBUTING.md +82 -0
- package/Dockerfile +73 -0
- package/GOVERNANCE.md +62 -0
- package/README.md +323 -6
- package/SECURITY.md +70 -0
- package/bench-results/replay-cache-full-1777891033256.json +10 -0
- package/bench-results/replay-cache-full-1777896317488.json +10 -0
- package/bench-results/replay-cache-full-1777900993184.json +10 -0
- package/bench-results/replay-cache-full-1777901019285.json +10 -0
- package/bench-results/replay-cache-quick-1777870170126.json +10 -0
- package/bench-results/signature-profiles-quick-1775875160079.json +85 -0
- package/bench-results/signature-profiles-quick-1775983539716.json +85 -0
- package/bench-results/signature-profiles-quick-1776237913190.json +85 -0
- package/bench-results/wire-codecs-full-1777891019803.json +93 -0
- package/bench-results/wire-codecs-full-1777896260964.json +93 -0
- package/bench-results/wire-codecs-full-1777901004247.json +93 -0
- package/bench-results/wire-codecs-quick-1775972879056.json +93 -0
- package/bench-results/wire-codecs-quick-1775983541111.json +93 -0
- package/bench-results/wire-codecs-quick-1776237914299.json +93 -0
- package/bench-results/wire-codecs-quick-1777841285236.json +93 -0
- package/bench-results/wire-codecs-quick-1777841321772.json +93 -0
- package/bench-results/wire-codecs-quick-1777841330408.json +93 -0
- package/bench-results/wire-codecs-quick-1777852886082.json +93 -0
- package/bench-results/wire-codecs-quick-1777852988773.json +93 -0
- package/bench-results/wire-codecs-quick-1777870188095.json +93 -0
- package/bench-results/wire-codecs-quick-1777870263918.json +93 -0
- package/bench-results/wire-codecs-quick-1777870455034.json +93 -0
- package/bench-results/wire-codecs-quick-1778816163081.json +93 -0
- package/bench-results/wire-codecs-quick-1778843936130.json +93 -0
- package/bin/7h3.ts +385 -0
- package/conformance/7h3_v0_1.json +77 -0
- package/conformance/7h3_v0_1_binary.json +20 -0
- package/conformance/aip_v0_1_binary.json +20 -0
- package/docker-compose.yaml +77 -0
- package/docs/ADOPTION_PLAN.md +120 -0
- package/docs/AGENTS.md +77 -0
- package/docs/AIP_RFC_v0.1.md +97 -0
- package/docs/AI_DECISION_CARD.md +122 -0
- package/docs/AI_RUNTIME_POLICY.json +126 -0
- package/docs/AI_RUNTIME_POLICY.yaml +110 -0
- package/docs/BACKPRESSURE_TUNING.md +65 -0
- package/docs/BENCHMARK_CLAIM_MATRIX.md +42 -0
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +169 -0
- package/docs/BINARY_CODEC_BENCH.md +23 -0
- package/docs/CLEAN_CLONE_RUNBOOK.md +36 -0
- package/docs/CLOCK_SKEW_POLICY.md +30 -0
- package/docs/DISTRIBUTED_REPLAY.md +142 -0
- package/docs/FUZZ_CAMPAIGN.md +121 -0
- package/docs/GATEWAY.md +195 -0
- package/docs/KEY_MANAGEMENT_POLICY.md +53 -0
- package/docs/KEY_REVOCATION.md +69 -0
- package/docs/MCP_WRAPPER.md +159 -0
- package/docs/MIGRATION_GUIDE.md +40 -0
- package/docs/OPERATORS.md +184 -0
- package/docs/PERF_REGRESSION_POLICY.md +34 -0
- package/docs/PROJECT_EXAMINATION_2026-05-31.md +219 -0
- package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +135 -0
- package/docs/RELEASE_GATE.md +25 -0
- package/docs/RELEASE_NOTES_v0.1.0.md +54 -0
- package/docs/SECURITY_REVIEW_2026-06-05.md +165 -0
- package/docs/TELEMETRY.md +41 -0
- package/docs/THREAT_MODEL.md +89 -0
- package/docs/VERSIONING_POLICY.md +30 -0
- package/docs/assets/banner.png +0 -0
- package/eslint.config.js +15 -0
- package/fuzz/ts/harness-decode.ts +136 -0
- package/fuzz/ts/harness-verify.ts +121 -0
- package/fuzz/ts/run.ts +35 -0
- package/mcp-server/README.md +38 -0
- package/mcp-server/package-lock.json +1187 -0
- package/mcp-server/package.json +35 -0
- package/mcp-server/src/index.ts +236 -0
- package/mcp-server/tsconfig.json +14 -0
- package/package.json +79 -13
- package/scripts/aip-framework-quickstart.ts +110 -0
- package/scripts/aip-mcp-gateway.ts +38 -0
- package/scripts/aip-mcp-wrap-demo.ts +72 -0
- package/scripts/aip-quickstart.ts +60 -0
- package/scripts/bench-diff.ts +118 -0
- package/scripts/bench-protocol-e2e.ts +937 -0
- package/scripts/bench-protocol-openloop.ts +1397 -0
- package/scripts/bench-replay-cache.ts +76 -0
- package/scripts/bench-signature-profiles.ts +180 -0
- package/scripts/bench-wire-codecs.ts +161 -0
- package/scripts/build-binary-conformance.ts +36 -0
- package/scripts/build-release-dashboard.ts +175 -0
- package/scripts/canary-rollout.ts +38 -0
- package/scripts/mcpGatewayCli.test.ts +116 -0
- package/scripts/prepare-aip-package.ts +88 -0
- package/scripts/regen-conformance-sigs.ts +18 -0
- package/scripts/release-gate.ts +19 -0
- package/scripts/validate-runtime-policy.ts +18 -0
- package/sdk/browser/index.test.ts +162 -0
- package/sdk/browser/index.ts +257 -0
- package/sdk/browser/package.json +13 -0
- package/sdk/go/go.mod +3 -0
- package/sdk/go/http.go +135 -0
- package/sdk/go/protocol.go +324 -0
- package/sdk/go/protocol_test.go +334 -0
- package/sdk/go/webhook.go +136 -0
- package/sdk/python/README.md +18 -0
- package/sdk/python/protocol_7h3/__init__.py +46 -0
- package/sdk/python/protocol_7h3/http.py +212 -0
- package/sdk/python/protocol_7h3/keys.py +149 -0
- package/sdk/python/protocol_7h3/protocol.py +525 -0
- package/sdk/python/protocol_7h3/queue.py +118 -0
- package/sdk/python/protocol_7h3/webhook.py +116 -0
- package/sdk/python/pyproject.toml +40 -0
- package/sdk/python/tests/test_conformance.py +110 -0
- package/sdk/python/tests/test_http.py +305 -0
- package/sdk/python/tests/test_keys.py +417 -0
- package/sdk/python/tests/test_queue.py +120 -0
- package/sdk/python/tests/test_webhook.py +345 -0
- package/sdk/rust/Cargo.lock +371 -0
- package/sdk/rust/Cargo.toml +25 -0
- package/sdk/rust/README.md +31 -0
- package/sdk/rust/fuzz/Cargo.toml +29 -0
- package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +46 -0
- package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +11 -0
- package/sdk/rust/src/bin/aip_mcp_gateway.rs +59 -0
- package/sdk/rust/src/http.rs +145 -0
- package/sdk/rust/src/keys.rs +161 -0
- package/sdk/rust/src/lib.rs +688 -0
- package/sdk/rust/src/queue.rs +79 -0
- package/sdk/rust/src/webhook.rs +86 -0
- package/sdk/rust/tests/conformance.rs +148 -0
- package/sdk/rust/tests/gateway.rs +130 -0
- package/sdk/rust/tests/http_webhook_queue.rs +201 -0
- package/sdk/rust/tests/keys.rs +189 -0
- package/src/agentAdapter.test.ts +48 -0
- package/src/agentAdapter.ts +56 -0
- package/src/auditLog.test.ts +145 -0
- package/src/auditLog.ts +147 -0
- package/src/conformance.test.ts +136 -0
- package/src/conformanceVectors.ts +99 -0
- package/src/frameworkAdapters.test.ts +290 -0
- package/src/frameworkAdapters.ts +261 -0
- package/src/gateway.test.ts +343 -0
- package/src/gateway.ts +171 -0
- package/src/grpcBinding.test.ts +211 -0
- package/src/grpcBinding.ts +103 -0
- package/src/httpBinding.test.ts +376 -0
- package/src/httpBinding.ts +163 -0
- package/src/index.ts +32 -0
- package/src/keyInfra.test.ts +278 -0
- package/src/keyInfra.ts +228 -0
- package/src/keyRegistry.ts +59 -0
- package/src/keyRotation.test.ts +78 -0
- package/src/keyRotation.ts +72 -0
- package/src/mcpGateway.test.ts +129 -0
- package/src/mcpGateway.ts +250 -0
- package/src/mcpTransports.test.ts +92 -0
- package/src/mcpTransports.ts +169 -0
- package/src/mcpWrapper.test.ts +179 -0
- package/src/mcpWrapper.ts +206 -0
- package/src/policyEnforcer.test.ts +99 -0
- package/src/policyEnforcer.ts +169 -0
- package/src/policyTelemetryFeedback.test.ts +25 -0
- package/src/policyTelemetryFeedback.ts +38 -0
- package/src/protocol.bench.ts +37 -0
- package/src/protocol.test.ts +155 -0
- package/src/protocol.ts +413 -0
- package/src/protocolAgent.test.ts +105 -0
- package/src/protocolAgent.ts +169 -0
- package/src/protocolBinary.test.ts +165 -0
- package/src/protocolBinary.ts +312 -0
- package/src/protocolCapabilities.ts +70 -0
- package/src/protocolFuzz.advanced.test.ts +235 -0
- package/src/protocolFuzz.test.ts +111 -0
- package/src/protocolNegative.test.ts +97 -0
- package/src/protocolReplay.test.ts +71 -0
- package/src/protocolReplay.ts +194 -0
- package/src/protocolTransport.test.ts +556 -0
- package/src/protocolTransport.ts +483 -0
- package/src/queueBinding.test.ts +130 -0
- package/src/queueBinding.ts +102 -0
- package/src/rateLimiter.test.ts +96 -0
- package/src/rateLimiter.ts +46 -0
- package/src/redisClient.ts +140 -0
- package/src/redisIntegration.test.ts +134 -0
- package/src/replayStores.test.ts +141 -0
- package/src/replayStores.ts +82 -0
- package/src/revocation.test.ts +98 -0
- package/src/revocation.ts +0 -0
- package/src/routePolicy.test.ts +87 -0
- package/src/routePolicy.ts +72 -0
- package/src/runtimePolicy.test.ts +49 -0
- package/src/runtimePolicy.ts +81 -0
- package/src/runtimePolicyManager.test.ts +29 -0
- package/src/runtimePolicyManager.ts +50 -0
- package/src/runtimePolicyPresets.ts +43 -0
- package/src/signedResponse.test.ts +111 -0
- package/src/signedResponse.ts +83 -0
- package/src/webhookBinding.test.ts +144 -0
- package/src/webhookBinding.ts +115 -0
- package/src/wsBinding.test.ts +221 -0
- package/src/wsBinding.ts +100 -0
- package/tsconfig.json +15 -0
- package/tsconfig.lib.json +23 -0
- package/vite.lib.config.ts +16 -0
- package/agentAdapter.d.ts +0 -26
- package/conformanceVectors.d.ts +0 -20
- package/frameworkAdapters.d.ts +0 -72
- package/index.d.ts +0 -20
- package/index.js +0 -1702
- package/keyRotation.d.ts +0 -20
- package/mcpGateway.d.ts +0 -37
- package/mcpTransports.d.ts +0 -62
- package/mcpWrapper.d.ts +0 -83
- package/policyEnforcer.d.ts +0 -50
- package/policyTelemetryFeedback.d.ts +0 -11
- package/protocol.d.ts +0 -66
- package/protocolAgent.d.ts +0 -58
- package/protocolBinary.d.ts +0 -8
- package/protocolCapabilities.d.ts +0 -24
- package/protocolReplay.d.ts +0 -35
- package/protocolTransport.d.ts +0 -73
- package/redisClient.d.ts +0 -49
- package/replayStores.d.ts +0 -32
- package/revocation.d.ts +0 -71
- package/runtimePolicy.d.ts +0 -24
- package/runtimePolicyManager.d.ts +0 -15
- package/runtimePolicyPresets.d.ts +0 -11
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { loadRuntimePolicy, type LoadRuntimePolicyOptions, type RuntimePolicy } from './runtimePolicy'
|
|
2
|
+
|
|
3
|
+
export type RuntimeMode =
|
|
4
|
+
| 'http'
|
|
5
|
+
| 'ws'
|
|
6
|
+
| 'http-binary'
|
|
7
|
+
| 'http-batch'
|
|
8
|
+
| 'http-binary-batch'
|
|
9
|
+
| 'ws-batch'
|
|
10
|
+
| 'ws-binary'
|
|
11
|
+
| 'ws-binary-batch'
|
|
12
|
+
|
|
13
|
+
export interface ModeSelectionInput {
|
|
14
|
+
concurrency: number
|
|
15
|
+
latencySensitive?: boolean
|
|
16
|
+
compatibilityFirst?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TuningInput {
|
|
20
|
+
trafficClass: 'low' | 'medium' | 'high'
|
|
21
|
+
p99Ms?: number
|
|
22
|
+
dropPct?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TuningDecision {
|
|
26
|
+
batchSizeRange: [number, number]
|
|
27
|
+
inflightCapRange: [number, number]
|
|
28
|
+
retryBackoffMsRange: [number, number]
|
|
29
|
+
actions: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RetryInput {
|
|
33
|
+
error: unknown
|
|
34
|
+
attempt: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface InvariantConfig {
|
|
38
|
+
signatureVerification: boolean
|
|
39
|
+
canonicalization: boolean
|
|
40
|
+
replayDefense: boolean
|
|
41
|
+
ttlClockSkewEnforcement: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function asNumberPair(value: unknown, label: string): [number, number] {
|
|
45
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
46
|
+
throw new Error(`Invalid policy tuning range '${label}': expected [min,max]`)
|
|
47
|
+
}
|
|
48
|
+
const min = Number(value[0])
|
|
49
|
+
const max = Number(value[1])
|
|
50
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || min <= 0 || max < min) {
|
|
51
|
+
throw new Error(`Invalid policy tuning range '${label}': expected positive ascending numbers`)
|
|
52
|
+
}
|
|
53
|
+
return [Math.floor(min), Math.floor(max)]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function includeHttpLiteral(mode: string): mode is RuntimeMode {
|
|
57
|
+
return (
|
|
58
|
+
mode === 'http' ||
|
|
59
|
+
mode === 'ws' ||
|
|
60
|
+
mode === 'http-binary' ||
|
|
61
|
+
mode === 'http-batch' ||
|
|
62
|
+
mode === 'http-binary-batch' ||
|
|
63
|
+
mode === 'ws-batch' ||
|
|
64
|
+
mode === 'ws-binary' ||
|
|
65
|
+
mode === 'ws-binary-batch'
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createPolicyEnforcer(policy: RuntimePolicy) {
|
|
70
|
+
const dropPctMax = Number((policy.slo_defaults ?? {}).drop_pct_max ?? 0.1)
|
|
71
|
+
const p99MsMax = Number((policy.slo_defaults ?? {}).p99_ms_max ?? 20)
|
|
72
|
+
const maxRetryAttempts = Number((policy.tuning ?? {}).max_retry_attempts ?? 3)
|
|
73
|
+
const retriableStatusCodes = Array.isArray((policy.tuning ?? {}).retriable_status_codes)
|
|
74
|
+
? ((policy.tuning ?? {}).retriable_status_codes as unknown[])
|
|
75
|
+
.map((value) => Number(value))
|
|
76
|
+
.filter((value) => Number.isFinite(value))
|
|
77
|
+
: [503]
|
|
78
|
+
|
|
79
|
+
const tuning = policy.tuning ?? {}
|
|
80
|
+
const batchSize = tuning.batch_size as Record<string, unknown>
|
|
81
|
+
const inflightCap = tuning.inflight_cap as Record<string, unknown>
|
|
82
|
+
const retryBackoff = tuning.retry_backoff_ms as Record<string, unknown>
|
|
83
|
+
|
|
84
|
+
const getRanges = (trafficClass: 'low' | 'medium' | 'high') => ({
|
|
85
|
+
batchSizeRange: asNumberPair(batchSize?.[`${trafficClass}_traffic`], `batch_size.${trafficClass}_traffic`),
|
|
86
|
+
inflightCapRange: asNumberPair(inflightCap?.[`${trafficClass}_traffic`], `inflight_cap.${trafficClass}_traffic`),
|
|
87
|
+
retryBackoffMsRange: asNumberPair(retryBackoff?.[`${trafficClass}_traffic`], `retry_backoff_ms.${trafficClass}_traffic`),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
function selectMode(input: ModeSelectionInput): RuntimeMode {
|
|
91
|
+
if (input.concurrency >= 100) return 'http-binary-batch'
|
|
92
|
+
if (input.latencySensitive) return 'ws-batch'
|
|
93
|
+
if (input.compatibilityFirst) return 'http'
|
|
94
|
+
return 'ws-batch'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function tune(input: TuningInput): TuningDecision {
|
|
98
|
+
const ranges = getRanges(input.trafficClass)
|
|
99
|
+
const actions: string[] = []
|
|
100
|
+
|
|
101
|
+
if (typeof input.dropPct === 'number' && input.dropPct > dropPctMax) {
|
|
102
|
+
actions.push('reduce inflight cap')
|
|
103
|
+
actions.push('reduce batch size')
|
|
104
|
+
actions.push('increase retry backoff')
|
|
105
|
+
actions.push('switch to binary-batch mode')
|
|
106
|
+
}
|
|
107
|
+
if (typeof input.p99Ms === 'number' && input.p99Ms > p99MsMax) {
|
|
108
|
+
actions.push('reduce batch size')
|
|
109
|
+
actions.push('reduce inflight cap')
|
|
110
|
+
actions.push('re-run adaptive benchmark')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...ranges,
|
|
115
|
+
actions,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function shouldRetry(input: RetryInput): boolean {
|
|
120
|
+
if (input.attempt >= maxRetryAttempts) return false
|
|
121
|
+
const message = input.error instanceof Error ? input.error.message : String(input.error)
|
|
122
|
+
const statusFromMessage = /http status\s+(\d+)/i.exec(message)
|
|
123
|
+
const statusCode = statusFromMessage ? Number(statusFromMessage[1]) : NaN
|
|
124
|
+
return Number.isFinite(statusCode) && retriableStatusCodes.includes(statusCode)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function assertInvariant(config: InvariantConfig): void {
|
|
128
|
+
const failures: string[] = []
|
|
129
|
+
if (!config.signatureVerification) failures.push('signatureVerification=false')
|
|
130
|
+
if (!config.canonicalization) failures.push('canonicalization=false')
|
|
131
|
+
if (!config.replayDefense) failures.push('replayDefense=false')
|
|
132
|
+
if (!config.ttlClockSkewEnforcement) failures.push('ttlClockSkewEnforcement=false')
|
|
133
|
+
if (failures.length > 0) {
|
|
134
|
+
throw new Error(`Invariant violation: ${failures.join(', ')}`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
selectMode,
|
|
140
|
+
tune,
|
|
141
|
+
shouldRetry,
|
|
142
|
+
assertInvariant,
|
|
143
|
+
metadata: {
|
|
144
|
+
policyName: policy.name,
|
|
145
|
+
policyVersion: policy.version,
|
|
146
|
+
policyStatus: policy.status,
|
|
147
|
+
enforcedSlo: {
|
|
148
|
+
dropPctMax,
|
|
149
|
+
p99MsMax,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type PolicyEnforcer = ReturnType<typeof createPolicyEnforcer>
|
|
156
|
+
|
|
157
|
+
export type BootstrapRuntimePolicyEnforcerOptions = LoadRuntimePolicyOptions
|
|
158
|
+
|
|
159
|
+
export async function bootstrapRuntimePolicyEnforcer(
|
|
160
|
+
options: BootstrapRuntimePolicyEnforcerOptions = {},
|
|
161
|
+
): Promise<{ policy: RuntimePolicy; enforcer: PolicyEnforcer }> {
|
|
162
|
+
const policy = await loadRuntimePolicy(options)
|
|
163
|
+
const enforcer = createPolicyEnforcer(policy)
|
|
164
|
+
return { policy, enforcer }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function isRuntimeMode(value: string): value is RuntimeMode {
|
|
168
|
+
return includeHttpLiteral(value)
|
|
169
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { recommendPolicyAdjustments } from './policyTelemetryFeedback'
|
|
3
|
+
|
|
4
|
+
describe('policy telemetry feedback', () => {
|
|
5
|
+
it('returns critical recommendation for severe drop/latency', () => {
|
|
6
|
+
const rec = recommendPolicyAdjustments({
|
|
7
|
+
mode: 'http',
|
|
8
|
+
p99Ms: 120,
|
|
9
|
+
dropPct: 5,
|
|
10
|
+
concurrency: 200,
|
|
11
|
+
})
|
|
12
|
+
expect(rec.severity).toBe('critical')
|
|
13
|
+
expect(rec.actions.length).toBeGreaterThan(0)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns normal when metrics are healthy', () => {
|
|
17
|
+
const rec = recommendPolicyAdjustments({
|
|
18
|
+
mode: 'http-binary-batch',
|
|
19
|
+
p99Ms: 8,
|
|
20
|
+
dropPct: 0,
|
|
21
|
+
concurrency: 100,
|
|
22
|
+
})
|
|
23
|
+
expect(rec.severity).toBe('normal')
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface TelemetryFeedbackInput {
|
|
2
|
+
mode: string
|
|
3
|
+
p99Ms: number
|
|
4
|
+
dropPct: number
|
|
5
|
+
concurrency: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TelemetryFeedbackRecommendation {
|
|
9
|
+
severity: 'normal' | 'warn' | 'critical'
|
|
10
|
+
actions: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function recommendPolicyAdjustments(input: TelemetryFeedbackInput): TelemetryFeedbackRecommendation {
|
|
14
|
+
const actions: string[] = []
|
|
15
|
+
let severity: TelemetryFeedbackRecommendation['severity'] = 'normal'
|
|
16
|
+
|
|
17
|
+
if (input.dropPct > 0.1) {
|
|
18
|
+
actions.push('reduce inflight cap')
|
|
19
|
+
actions.push('reduce batch size')
|
|
20
|
+
actions.push('increase retry backoff')
|
|
21
|
+
if (input.mode === 'http' && input.concurrency >= 100) {
|
|
22
|
+
actions.push('switch to http-binary-batch')
|
|
23
|
+
}
|
|
24
|
+
severity = input.dropPct > 2 ? 'critical' : 'warn'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (input.p99Ms > 20) {
|
|
28
|
+
actions.push('reduce batch size')
|
|
29
|
+
actions.push('run adaptive benchmark and re-baseline')
|
|
30
|
+
if (severity === 'normal') severity = 'warn'
|
|
31
|
+
if (input.p99Ms > 100) severity = 'critical'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
severity,
|
|
36
|
+
actions,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest'
|
|
2
|
+
import { canonicalizeEnvelope, createEnvelope, signEnvelopeHmac, verifyEnvelopeHmac } from './protocol'
|
|
3
|
+
import { decodeEnvelope, encodeEnvelope } from './protocolTransport'
|
|
4
|
+
|
|
5
|
+
const unsigned = createEnvelope({
|
|
6
|
+
sender: 'agent.alpha',
|
|
7
|
+
recipient: 'agent.beta',
|
|
8
|
+
intent: 'TASK',
|
|
9
|
+
content: 'route-optimize:alpha-beta',
|
|
10
|
+
capability: 'task.plan',
|
|
11
|
+
correlationId: 'corr-1',
|
|
12
|
+
messageId: 'bench-1',
|
|
13
|
+
nonce: 'bench-nonce',
|
|
14
|
+
nowMs: 1_000,
|
|
15
|
+
ttlMs: 120_000,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('protocol throughput', () => {
|
|
19
|
+
bench('canonicalize envelope', () => {
|
|
20
|
+
canonicalizeEnvelope(unsigned)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
bench('encode/decode envelope json', () => {
|
|
24
|
+
const encoded = encodeEnvelope({ ...unsigned })
|
|
25
|
+
decodeEnvelope(encoded)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
bench('encode/decode envelope compact', () => {
|
|
29
|
+
const encoded = encodeEnvelope({ ...unsigned }, 'compact')
|
|
30
|
+
decodeEnvelope(encoded)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
bench('sign + verify envelope', async () => {
|
|
34
|
+
const signed = await signEnvelopeHmac(unsigned, 'bench-secret', 'bench-k1')
|
|
35
|
+
await verifyEnvelopeHmac(signed, 'bench-secret')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
canonicalizeEnvelope,
|
|
4
|
+
createEnvelope,
|
|
5
|
+
generateEd25519KeypairBase64Url,
|
|
6
|
+
signCanonicalPayloadHmac,
|
|
7
|
+
signEnvelopeEd25519,
|
|
8
|
+
signEnvelopeHmac,
|
|
9
|
+
verifyCanonicalPayloadEd25519,
|
|
10
|
+
validateEnvelope,
|
|
11
|
+
verifyCanonicalPayloadHmac,
|
|
12
|
+
verifyEnvelopeEd25519,
|
|
13
|
+
verifyEnvelopeHmac,
|
|
14
|
+
} from './protocol'
|
|
15
|
+
|
|
16
|
+
describe('GLUV AIP protocol', () => {
|
|
17
|
+
it('uses stable canonical field order', () => {
|
|
18
|
+
const envelope = createEnvelope({
|
|
19
|
+
sender: 'agent.alpha',
|
|
20
|
+
recipient: 'agent.beta',
|
|
21
|
+
intent: 'TASK',
|
|
22
|
+
content: 'compute route',
|
|
23
|
+
capability: 'task.plan',
|
|
24
|
+
correlationId: 'c-1',
|
|
25
|
+
messageId: 'm0',
|
|
26
|
+
nonce: 'n0',
|
|
27
|
+
nowMs: 500,
|
|
28
|
+
ttlMs: 10_000,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(canonicalizeEnvelope(envelope)).toBe(
|
|
32
|
+
'{"body":{"capability":"task.plan","content":"compute route","correlationId":"c-1","intent":"TASK"},"header":{"messageId":"m0","nonce":"n0","recipient":"agent.beta","sender":"agent.alpha","timestampMs":500,"ttlMs":10000,"version":"7h3/0.1"}}',
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('canonicalizes envelopes deterministically', () => {
|
|
37
|
+
const envelope = createEnvelope({
|
|
38
|
+
sender: 'agent.alpha',
|
|
39
|
+
recipient: 'agent.beta',
|
|
40
|
+
intent: 'TASK',
|
|
41
|
+
content: 'compute route',
|
|
42
|
+
messageId: 'm1',
|
|
43
|
+
nonce: 'n1',
|
|
44
|
+
nowMs: 1000,
|
|
45
|
+
ttlMs: 10000,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const c1 = canonicalizeEnvelope(envelope)
|
|
49
|
+
const c2 = canonicalizeEnvelope({ header: envelope.header, body: envelope.body })
|
|
50
|
+
expect(c1).toBe(c2)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('signs and verifies with HMAC', async () => {
|
|
54
|
+
const unsigned = createEnvelope({
|
|
55
|
+
sender: 'agent.alpha',
|
|
56
|
+
intent: 'PING',
|
|
57
|
+
content: 'hello',
|
|
58
|
+
messageId: 'm2',
|
|
59
|
+
nonce: 'n2',
|
|
60
|
+
nowMs: 2000,
|
|
61
|
+
ttlMs: 10000,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const signed = await signEnvelopeHmac(unsigned, 'secret-key', 'dev-1')
|
|
65
|
+
const ok = await verifyEnvelopeHmac(signed, 'secret-key')
|
|
66
|
+
expect(ok).toBe(true)
|
|
67
|
+
|
|
68
|
+
const tampered = {
|
|
69
|
+
...signed,
|
|
70
|
+
body: {
|
|
71
|
+
...signed.body,
|
|
72
|
+
content: 'tampered',
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
const bad = await verifyEnvelopeHmac(tampered, 'secret-key')
|
|
76
|
+
expect(bad).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('signs and verifies canonical payload directly', async () => {
|
|
80
|
+
const unsigned = createEnvelope({
|
|
81
|
+
sender: 'agent.alpha',
|
|
82
|
+
intent: 'PING',
|
|
83
|
+
content: 'hello',
|
|
84
|
+
messageId: 'm2b',
|
|
85
|
+
nonce: 'n2b',
|
|
86
|
+
nowMs: 2000,
|
|
87
|
+
ttlMs: 10000,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const payload = canonicalizeEnvelope(unsigned)
|
|
91
|
+
const sig = await signCanonicalPayloadHmac(payload, 'secret-key')
|
|
92
|
+
expect(await verifyCanonicalPayloadHmac(payload, sig, 'secret-key')).toBe(true)
|
|
93
|
+
expect(await verifyCanonicalPayloadHmac(`${payload}x`, sig, 'secret-key')).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('signs and verifies with Ed25519', async () => {
|
|
97
|
+
const keys = await generateEd25519KeypairBase64Url()
|
|
98
|
+
const unsigned = createEnvelope({
|
|
99
|
+
sender: 'agent.alpha',
|
|
100
|
+
intent: 'TASK',
|
|
101
|
+
content: 'route:ed25519',
|
|
102
|
+
messageId: 'm-ed1',
|
|
103
|
+
nonce: 'n-ed1',
|
|
104
|
+
nowMs: 3000,
|
|
105
|
+
ttlMs: 10000,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const signed = await signEnvelopeEd25519(unsigned, keys.privateKey, 'ed-1')
|
|
109
|
+
const ok = await verifyEnvelopeEd25519(signed, keys.publicKey)
|
|
110
|
+
expect(ok).toBe(true)
|
|
111
|
+
|
|
112
|
+
const tampered = {
|
|
113
|
+
...signed,
|
|
114
|
+
body: {
|
|
115
|
+
...signed.body,
|
|
116
|
+
content: 'tampered',
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
const bad = await verifyEnvelopeEd25519(tampered, keys.publicKey)
|
|
120
|
+
expect(bad).toBe(false)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('signs and verifies Ed25519 canonical payload directly', async () => {
|
|
124
|
+
const keys = await generateEd25519KeypairBase64Url()
|
|
125
|
+
const unsigned = createEnvelope({
|
|
126
|
+
sender: 'agent.alpha',
|
|
127
|
+
intent: 'PING',
|
|
128
|
+
content: 'hello-ed25519',
|
|
129
|
+
messageId: 'm-ed2',
|
|
130
|
+
nonce: 'n-ed2',
|
|
131
|
+
nowMs: 3100,
|
|
132
|
+
ttlMs: 10000,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const payload = canonicalizeEnvelope(unsigned)
|
|
136
|
+
const sig = await signEnvelopeEd25519(unsigned, keys.privateKey, 'ed-2')
|
|
137
|
+
expect(sig.signature?.alg).toBe('ED25519')
|
|
138
|
+
expect(await verifyCanonicalPayloadEd25519(payload, sig.signature?.value ?? '', keys.publicKey)).toBe(true)
|
|
139
|
+
expect(await verifyCanonicalPayloadEd25519(`${payload}x`, sig.signature?.value ?? '', keys.publicKey)).toBe(false)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('flags expired messages', () => {
|
|
143
|
+
const unsigned = createEnvelope({
|
|
144
|
+
sender: 'agent.alpha',
|
|
145
|
+
intent: 'RESULT',
|
|
146
|
+
content: 'done',
|
|
147
|
+
messageId: 'm3',
|
|
148
|
+
nonce: 'n3',
|
|
149
|
+
nowMs: 0,
|
|
150
|
+
ttlMs: 100,
|
|
151
|
+
})
|
|
152
|
+
const diagnostics = validateEnvelope({ ...unsigned }, 500)
|
|
153
|
+
expect(diagnostics.some((d) => d.message === 'Message TTL expired')).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
})
|