@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.
Files changed (248) hide show
  1. package/README.md +6 -323
  2. package/agentAdapter.d.ts +26 -0
  3. package/auditLog.d.ts +54 -0
  4. package/capability.d.ts +67 -0
  5. package/cborCodec.d.ts +34 -0
  6. package/conformanceVectors.d.ts +20 -0
  7. package/encryption.d.ts +85 -0
  8. package/envelopeCbor.d.ts +33 -0
  9. package/frameworkAdapters.d.ts +72 -0
  10. package/gateway.d.ts +53 -0
  11. package/grpcBinding.d.ts +29 -0
  12. package/httpBinding.d.ts +50 -0
  13. package/index.d.ts +39 -0
  14. package/index.js +3662 -0
  15. package/keyInfra.d.ts +64 -0
  16. package/keyRegistry.d.ts +9 -0
  17. package/keyRotation.d.ts +20 -0
  18. package/mcpGateway.d.ts +37 -0
  19. package/mcpTransports.d.ts +62 -0
  20. package/mcpWrapper.d.ts +83 -0
  21. package/otel.d.ts +65 -0
  22. package/package.json +13 -79
  23. package/policyEnforcer.d.ts +50 -0
  24. package/policyTelemetryFeedback.d.ts +11 -0
  25. package/protocol.d.ts +66 -0
  26. package/protocolAgent.d.ts +58 -0
  27. package/protocolBinary.d.ts +8 -0
  28. package/protocolCapabilities.d.ts +24 -0
  29. package/protocolReplay.d.ts +35 -0
  30. package/protocolTransport.d.ts +73 -0
  31. package/queueBinding.d.ts +43 -0
  32. package/rateLimiter.d.ts +18 -0
  33. package/{src/redisClient.ts → redisClient.d.ts} +25 -75
  34. package/replayStores.d.ts +98 -0
  35. package/revocation.d.ts +71 -0
  36. package/routePolicy.d.ts +30 -0
  37. package/runtimePolicy.d.ts +24 -0
  38. package/runtimePolicyManager.d.ts +15 -0
  39. package/runtimePolicyPresets.d.ts +11 -0
  40. package/signedResponse.d.ts +21 -0
  41. package/stream.d.ts +59 -0
  42. package/telemetry.d.ts +79 -0
  43. package/webhookBinding.d.ts +30 -0
  44. package/wsBinding.d.ts +52 -0
  45. package/.dockerignore +0 -19
  46. package/.github/dependabot.yml +0 -32
  47. package/.github/workflows/ci.yml +0 -31
  48. package/.github/workflows/publish.yml +0 -59
  49. package/.github/workflows/scorecard.yml +0 -37
  50. package/7h3.example.yaml +0 -125
  51. package/CHANGELOG.md +0 -92
  52. package/CONTRIBUTING.md +0 -82
  53. package/Dockerfile +0 -73
  54. package/GOVERNANCE.md +0 -62
  55. package/SECURITY.md +0 -70
  56. package/bench-results/replay-cache-full-1777891033256.json +0 -10
  57. package/bench-results/replay-cache-full-1777896317488.json +0 -10
  58. package/bench-results/replay-cache-full-1777900993184.json +0 -10
  59. package/bench-results/replay-cache-full-1777901019285.json +0 -10
  60. package/bench-results/replay-cache-quick-1777870170126.json +0 -10
  61. package/bench-results/signature-profiles-quick-1775875160079.json +0 -85
  62. package/bench-results/signature-profiles-quick-1775983539716.json +0 -85
  63. package/bench-results/signature-profiles-quick-1776237913190.json +0 -85
  64. package/bench-results/wire-codecs-full-1777891019803.json +0 -93
  65. package/bench-results/wire-codecs-full-1777896260964.json +0 -93
  66. package/bench-results/wire-codecs-full-1777901004247.json +0 -93
  67. package/bench-results/wire-codecs-quick-1775972879056.json +0 -93
  68. package/bench-results/wire-codecs-quick-1775983541111.json +0 -93
  69. package/bench-results/wire-codecs-quick-1776237914299.json +0 -93
  70. package/bench-results/wire-codecs-quick-1777841285236.json +0 -93
  71. package/bench-results/wire-codecs-quick-1777841321772.json +0 -93
  72. package/bench-results/wire-codecs-quick-1777841330408.json +0 -93
  73. package/bench-results/wire-codecs-quick-1777852886082.json +0 -93
  74. package/bench-results/wire-codecs-quick-1777852988773.json +0 -93
  75. package/bench-results/wire-codecs-quick-1777870188095.json +0 -93
  76. package/bench-results/wire-codecs-quick-1777870263918.json +0 -93
  77. package/bench-results/wire-codecs-quick-1777870455034.json +0 -93
  78. package/bench-results/wire-codecs-quick-1778816163081.json +0 -93
  79. package/bench-results/wire-codecs-quick-1778843936130.json +0 -93
  80. package/bin/7h3.ts +0 -385
  81. package/conformance/7h3_v0_1.json +0 -77
  82. package/conformance/7h3_v0_1_binary.json +0 -20
  83. package/conformance/aip_v0_1_binary.json +0 -20
  84. package/docker-compose.yaml +0 -77
  85. package/docs/ADOPTION_PLAN.md +0 -120
  86. package/docs/AGENTS.md +0 -77
  87. package/docs/AIP_RFC_v0.1.md +0 -97
  88. package/docs/AI_DECISION_CARD.md +0 -122
  89. package/docs/AI_RUNTIME_POLICY.json +0 -126
  90. package/docs/AI_RUNTIME_POLICY.yaml +0 -110
  91. package/docs/BACKPRESSURE_TUNING.md +0 -65
  92. package/docs/BENCHMARK_CLAIM_MATRIX.md +0 -42
  93. package/docs/BENCHMARK_REPORT_TEMPLATE.md +0 -169
  94. package/docs/BINARY_CODEC_BENCH.md +0 -23
  95. package/docs/CLEAN_CLONE_RUNBOOK.md +0 -36
  96. package/docs/CLOCK_SKEW_POLICY.md +0 -30
  97. package/docs/DISTRIBUTED_REPLAY.md +0 -142
  98. package/docs/FUZZ_CAMPAIGN.md +0 -121
  99. package/docs/GATEWAY.md +0 -195
  100. package/docs/KEY_MANAGEMENT_POLICY.md +0 -53
  101. package/docs/KEY_REVOCATION.md +0 -69
  102. package/docs/MCP_WRAPPER.md +0 -159
  103. package/docs/MIGRATION_GUIDE.md +0 -40
  104. package/docs/OPERATORS.md +0 -184
  105. package/docs/PERF_REGRESSION_POLICY.md +0 -34
  106. package/docs/PROJECT_EXAMINATION_2026-05-31.md +0 -219
  107. package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +0 -135
  108. package/docs/RELEASE_GATE.md +0 -25
  109. package/docs/RELEASE_NOTES_v0.1.0.md +0 -54
  110. package/docs/SECURITY_REVIEW_2026-06-05.md +0 -165
  111. package/docs/TELEMETRY.md +0 -41
  112. package/docs/THREAT_MODEL.md +0 -89
  113. package/docs/VERSIONING_POLICY.md +0 -30
  114. package/docs/assets/banner.png +0 -0
  115. package/eslint.config.js +0 -15
  116. package/fuzz/ts/harness-decode.ts +0 -136
  117. package/fuzz/ts/harness-verify.ts +0 -121
  118. package/fuzz/ts/run.ts +0 -35
  119. package/mcp-server/README.md +0 -38
  120. package/mcp-server/package-lock.json +0 -1187
  121. package/mcp-server/package.json +0 -35
  122. package/mcp-server/src/index.ts +0 -236
  123. package/mcp-server/tsconfig.json +0 -14
  124. package/scripts/aip-framework-quickstart.ts +0 -110
  125. package/scripts/aip-mcp-gateway.ts +0 -38
  126. package/scripts/aip-mcp-wrap-demo.ts +0 -72
  127. package/scripts/aip-quickstart.ts +0 -60
  128. package/scripts/bench-diff.ts +0 -118
  129. package/scripts/bench-protocol-e2e.ts +0 -937
  130. package/scripts/bench-protocol-openloop.ts +0 -1397
  131. package/scripts/bench-replay-cache.ts +0 -76
  132. package/scripts/bench-signature-profiles.ts +0 -180
  133. package/scripts/bench-wire-codecs.ts +0 -161
  134. package/scripts/build-binary-conformance.ts +0 -36
  135. package/scripts/build-release-dashboard.ts +0 -175
  136. package/scripts/canary-rollout.ts +0 -38
  137. package/scripts/mcpGatewayCli.test.ts +0 -116
  138. package/scripts/prepare-aip-package.ts +0 -88
  139. package/scripts/regen-conformance-sigs.ts +0 -18
  140. package/scripts/release-gate.ts +0 -19
  141. package/scripts/validate-runtime-policy.ts +0 -18
  142. package/sdk/browser/index.test.ts +0 -162
  143. package/sdk/browser/index.ts +0 -257
  144. package/sdk/browser/package.json +0 -13
  145. package/sdk/go/go.mod +0 -3
  146. package/sdk/go/http.go +0 -135
  147. package/sdk/go/protocol.go +0 -324
  148. package/sdk/go/protocol_test.go +0 -334
  149. package/sdk/go/webhook.go +0 -136
  150. package/sdk/python/README.md +0 -18
  151. package/sdk/python/protocol_7h3/__init__.py +0 -46
  152. package/sdk/python/protocol_7h3/http.py +0 -212
  153. package/sdk/python/protocol_7h3/keys.py +0 -149
  154. package/sdk/python/protocol_7h3/protocol.py +0 -525
  155. package/sdk/python/protocol_7h3/queue.py +0 -118
  156. package/sdk/python/protocol_7h3/webhook.py +0 -116
  157. package/sdk/python/pyproject.toml +0 -40
  158. package/sdk/python/tests/test_conformance.py +0 -110
  159. package/sdk/python/tests/test_http.py +0 -305
  160. package/sdk/python/tests/test_keys.py +0 -417
  161. package/sdk/python/tests/test_queue.py +0 -120
  162. package/sdk/python/tests/test_webhook.py +0 -345
  163. package/sdk/rust/Cargo.lock +0 -371
  164. package/sdk/rust/Cargo.toml +0 -25
  165. package/sdk/rust/README.md +0 -31
  166. package/sdk/rust/fuzz/Cargo.toml +0 -29
  167. package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
  168. package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
  169. package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
  170. package/sdk/rust/src/http.rs +0 -145
  171. package/sdk/rust/src/keys.rs +0 -161
  172. package/sdk/rust/src/lib.rs +0 -688
  173. package/sdk/rust/src/queue.rs +0 -79
  174. package/sdk/rust/src/webhook.rs +0 -86
  175. package/sdk/rust/tests/conformance.rs +0 -148
  176. package/sdk/rust/tests/gateway.rs +0 -130
  177. package/sdk/rust/tests/http_webhook_queue.rs +0 -201
  178. package/sdk/rust/tests/keys.rs +0 -189
  179. package/src/agentAdapter.test.ts +0 -48
  180. package/src/agentAdapter.ts +0 -56
  181. package/src/auditLog.test.ts +0 -145
  182. package/src/auditLog.ts +0 -147
  183. package/src/conformance.test.ts +0 -136
  184. package/src/conformanceVectors.ts +0 -99
  185. package/src/frameworkAdapters.test.ts +0 -290
  186. package/src/frameworkAdapters.ts +0 -261
  187. package/src/gateway.test.ts +0 -343
  188. package/src/gateway.ts +0 -171
  189. package/src/grpcBinding.test.ts +0 -211
  190. package/src/grpcBinding.ts +0 -103
  191. package/src/httpBinding.test.ts +0 -376
  192. package/src/httpBinding.ts +0 -163
  193. package/src/index.ts +0 -32
  194. package/src/keyInfra.test.ts +0 -278
  195. package/src/keyInfra.ts +0 -228
  196. package/src/keyRegistry.ts +0 -59
  197. package/src/keyRotation.test.ts +0 -78
  198. package/src/keyRotation.ts +0 -72
  199. package/src/mcpGateway.test.ts +0 -129
  200. package/src/mcpGateway.ts +0 -250
  201. package/src/mcpTransports.test.ts +0 -92
  202. package/src/mcpTransports.ts +0 -169
  203. package/src/mcpWrapper.test.ts +0 -179
  204. package/src/mcpWrapper.ts +0 -206
  205. package/src/policyEnforcer.test.ts +0 -99
  206. package/src/policyEnforcer.ts +0 -169
  207. package/src/policyTelemetryFeedback.test.ts +0 -25
  208. package/src/policyTelemetryFeedback.ts +0 -38
  209. package/src/protocol.bench.ts +0 -37
  210. package/src/protocol.test.ts +0 -155
  211. package/src/protocol.ts +0 -413
  212. package/src/protocolAgent.test.ts +0 -105
  213. package/src/protocolAgent.ts +0 -169
  214. package/src/protocolBinary.test.ts +0 -165
  215. package/src/protocolBinary.ts +0 -312
  216. package/src/protocolCapabilities.ts +0 -70
  217. package/src/protocolFuzz.advanced.test.ts +0 -235
  218. package/src/protocolFuzz.test.ts +0 -111
  219. package/src/protocolNegative.test.ts +0 -97
  220. package/src/protocolReplay.test.ts +0 -71
  221. package/src/protocolReplay.ts +0 -194
  222. package/src/protocolTransport.test.ts +0 -556
  223. package/src/protocolTransport.ts +0 -483
  224. package/src/queueBinding.test.ts +0 -130
  225. package/src/queueBinding.ts +0 -102
  226. package/src/rateLimiter.test.ts +0 -96
  227. package/src/rateLimiter.ts +0 -46
  228. package/src/redisIntegration.test.ts +0 -134
  229. package/src/replayStores.test.ts +0 -141
  230. package/src/replayStores.ts +0 -82
  231. package/src/revocation.test.ts +0 -98
  232. package/src/revocation.ts +0 -0
  233. package/src/routePolicy.test.ts +0 -87
  234. package/src/routePolicy.ts +0 -72
  235. package/src/runtimePolicy.test.ts +0 -49
  236. package/src/runtimePolicy.ts +0 -81
  237. package/src/runtimePolicyManager.test.ts +0 -29
  238. package/src/runtimePolicyManager.ts +0 -50
  239. package/src/runtimePolicyPresets.ts +0 -43
  240. package/src/signedResponse.test.ts +0 -111
  241. package/src/signedResponse.ts +0 -83
  242. package/src/webhookBinding.test.ts +0 -144
  243. package/src/webhookBinding.ts +0 -115
  244. package/src/wsBinding.test.ts +0 -221
  245. package/src/wsBinding.ts +0 -100
  246. package/tsconfig.json +0 -15
  247. package/tsconfig.lib.json +0 -23
  248. package/vite.lib.config.ts +0 -16
@@ -1,98 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import { InMemoryRedisLikeClient } from './redisClient'
3
- import {
4
- InMemoryRevocationStore,
5
- createRedisRevocationStore,
6
- withRevocationCheck,
7
- } from './revocation'
8
- import type { SignatureVerificationMaterial } from './protocol'
9
-
10
- describe('InMemoryRevocationStore', () => {
11
- it('reports a key as not revoked until it is revoked', async () => {
12
- const store = new InMemoryRevocationStore()
13
- expect(await store.isRevoked('agent.a', 'k1')).toBe(false)
14
- await store.revoke('agent.a', 'k1')
15
- expect(await store.isRevoked('agent.a', 'k1')).toBe(true)
16
- })
17
-
18
- it('scopes revocation to the exact (sender, keyId) pair', async () => {
19
- const store = new InMemoryRevocationStore()
20
- await store.revoke('agent.a', 'k1')
21
- expect(await store.isRevoked('agent.a', 'k2')).toBe(false)
22
- expect(await store.isRevoked('agent.b', 'k1')).toBe(false)
23
- })
24
-
25
- it('honors a time-bounded revocation (untilMs)', async () => {
26
- let now = 1000
27
- const store = new InMemoryRevocationStore({ now: () => now })
28
- await store.revoke('agent.a', 'k1', { untilMs: 2000 })
29
- expect(await store.isRevoked('agent.a', 'k1')).toBe(true)
30
- now = 2500
31
- expect(await store.isRevoked('agent.a', 'k1')).toBe(false)
32
- })
33
- })
34
-
35
- describe('createRedisRevocationStore', () => {
36
- it('revoke writes a key that isRevoked reads back', async () => {
37
- const store = createRedisRevocationStore(new InMemoryRedisLikeClient())
38
- expect(await store.isRevoked('agent.a', 'k1')).toBe(false)
39
- await store.revoke('agent.a', 'k1')
40
- expect(await store.isRevoked('agent.a', 'k1')).toBe(true)
41
- })
42
-
43
- it('caches reads so repeated checks within the freshness window hit Redis once', async () => {
44
- let now = 1000
45
- const client = new InMemoryRedisLikeClient({ now: () => now })
46
- const getSpy = vi.spyOn(client, 'get')
47
- const store = createRedisRevocationStore(client, { cacheTtlMs: 5000, now: () => now })
48
- await store.isRevoked('agent.a', 'k1')
49
- await store.isRevoked('agent.a', 'k1')
50
- expect(getSpy).toHaveBeenCalledOnce()
51
- now = 7000 // past the cache window
52
- await store.isRevoked('agent.a', 'k1')
53
- expect(getSpy).toHaveBeenCalledTimes(2)
54
- })
55
-
56
- it('fails closed by default: a Redis error means the key is treated as revoked', async () => {
57
- const onDegraded = vi.fn()
58
- const throwing = {
59
- set: async () => 'OK' as const,
60
- get: async () => {
61
- throw new Error('redis unreachable')
62
- },
63
- }
64
- const store = createRedisRevocationStore(throwing, { onDegraded })
65
- expect(await store.isRevoked('agent.a', 'k1')).toBe(true)
66
- expect(onDegraded).toHaveBeenCalledOnce()
67
- })
68
-
69
- it('errorBehavior "allow" treats a Redis error as not revoked', async () => {
70
- const throwing = {
71
- set: async () => 'OK' as const,
72
- get: async () => {
73
- throw new Error('redis unreachable')
74
- },
75
- }
76
- const store = createRedisRevocationStore(throwing, { errorBehavior: 'allow' })
77
- expect(await store.isRevoked('agent.a', 'k1')).toBe(false)
78
- })
79
- })
80
-
81
- describe('withRevocationCheck (signature resolver wrapper)', () => {
82
- const material: SignatureVerificationMaterial = { alg: 'HS256', secret: 's3cret' }
83
- const innerResolver = async () => material
84
- const signature = { alg: 'HS256' as const, keyId: 'k1', value: 'sig' }
85
-
86
- it('returns material for a key that is not revoked', async () => {
87
- const store = new InMemoryRevocationStore()
88
- const resolver = withRevocationCheck(innerResolver, store)
89
- expect(await resolver(signature, 'agent.a')).toEqual(material)
90
- })
91
-
92
- it('returns undefined (verification fails) for a revoked key', async () => {
93
- const store = new InMemoryRevocationStore()
94
- await store.revoke('agent.a', 'k1')
95
- const resolver = withRevocationCheck(innerResolver, store)
96
- expect(await resolver(signature, 'agent.a')).toBeUndefined()
97
- })
98
- })
package/src/revocation.ts DELETED
Binary file
@@ -1,87 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { matchGlob, matchPolicy, isAllowedSender, type RoutePolicy } from './routePolicy'
3
-
4
- describe('matchGlob', () => {
5
- it('exact match', () => {
6
- expect(matchGlob('/health', '/health')).toBe(true)
7
- expect(matchGlob('/health', '/healthz')).toBe(false)
8
- })
9
-
10
- it('single * does not cross segments', () => {
11
- expect(matchGlob('/api/*', '/api/users')).toBe(true)
12
- expect(matchGlob('/api/*', '/api/users/list')).toBe(false)
13
- expect(matchGlob('/api/*', '/api/')).toBe(true) // empty segment is still "within"
14
- })
15
-
16
- it('double ** matches any depth', () => {
17
- expect(matchGlob('/api/**', '/api/users')).toBe(true)
18
- expect(matchGlob('/api/**', '/api/users/list')).toBe(true)
19
- expect(matchGlob('/api/**', '/api/a/b/c/d')).toBe(true)
20
- expect(matchGlob('/api/**', '/other/users')).toBe(false)
21
- })
22
-
23
- it('? matches single non-slash char', () => {
24
- expect(matchGlob('/v?/api', '/v1/api')).toBe(true)
25
- expect(matchGlob('/v?/api', '/v12/api')).toBe(false)
26
- expect(matchGlob('/v?/api', '/v//api')).toBe(false)
27
- })
28
-
29
- it('handles regex metacharacters in literal path', () => {
30
- expect(matchGlob('/api/v1.0/health', '/api/v1.0/health')).toBe(true)
31
- expect(matchGlob('/api/v1.0/health', '/api/v100/health')).toBe(false)
32
- })
33
- })
34
-
35
- describe('matchPolicy', () => {
36
- const policies: RoutePolicy[] = [
37
- { path: '/health', require: 'none' },
38
- { path: '/api/admin/**', require: 'ed25519', allowedSenders: ['admin-agent'] },
39
- { path: '/api/*', require: 'any' },
40
- { path: '/webhook/**', require: 'hmac' },
41
- ]
42
-
43
- it('returns first matching policy (exact before wildcard)', () => {
44
- const result = matchPolicy(policies, '/health')
45
- expect(result?.require).toBe('none')
46
- })
47
-
48
- it('matches /api/users with /api/*', () => {
49
- const result = matchPolicy(policies, '/api/users')
50
- expect(result?.require).toBe('any')
51
- })
52
-
53
- it('/api/admin/delete matches /api/admin/** before /api/*', () => {
54
- const result = matchPolicy(policies, '/api/admin/delete')
55
- expect(result?.require).toBe('ed25519')
56
- })
57
-
58
- it('matches deep webhook path', () => {
59
- const result = matchPolicy(policies, '/webhook/stripe/events')
60
- expect(result?.require).toBe('hmac')
61
- })
62
-
63
- it('returns null for no match', () => {
64
- const result = matchPolicy(policies, '/unknown')
65
- expect(result).toBeNull()
66
- })
67
- })
68
-
69
- describe('isAllowedSender', () => {
70
- it('allows all senders when allowedSenders not set', () => {
71
- const policy: RoutePolicy = { path: '/api/*', require: 'any' }
72
- expect(isAllowedSender(policy, 'any-agent')).toBe(true)
73
- expect(isAllowedSender(policy, 'another-agent')).toBe(true)
74
- })
75
-
76
- it('allows only listed senders', () => {
77
- const policy: RoutePolicy = { path: '/api/*', require: 'ed25519', allowedSenders: ['trusted-agent', 'another-agent'] }
78
- expect(isAllowedSender(policy, 'trusted-agent')).toBe(true)
79
- expect(isAllowedSender(policy, 'another-agent')).toBe(true)
80
- expect(isAllowedSender(policy, 'unknown-agent')).toBe(false)
81
- })
82
-
83
- it('empty allowedSenders array allows all', () => {
84
- const policy: RoutePolicy = { path: '/api/*', require: 'any', allowedSenders: [] }
85
- expect(isAllowedSender(policy, 'anyone')).toBe(true)
86
- })
87
- })
@@ -1,72 +0,0 @@
1
- export type PolicyRequirement = 'ed25519' | 'hmac' | 'any' | 'none'
2
-
3
- export interface RoutePolicy {
4
- path: string
5
- require: PolicyRequirement
6
- rateLimit?: { requests: number; windowMs: number }
7
- allowedSenders?: string[]
8
- signResponse?: boolean
9
- }
10
-
11
- /**
12
- * Translate a glob pattern to a RegExp.
13
- * Supported:
14
- * - exact: '/health'
15
- * - '?' → any single non-slash char
16
- * - '*' → any chars within a single segment (no slash)
17
- * - '**' → any chars at any depth (including slashes)
18
- */
19
- export function matchGlob(pattern: string, path: string): boolean {
20
- // Build regex from pattern, handling ** before * to avoid double-expanding
21
- let regexStr = '^'
22
- let i = 0
23
- while (i < pattern.length) {
24
- const ch = pattern[i]
25
- if (ch === '*') {
26
- if (pattern[i + 1] === '*') {
27
- // Double star: matches anything including /
28
- regexStr += '.*'
29
- i += 2
30
- } else {
31
- // Single star: matches any non-slash sequence
32
- regexStr += '[^/]*'
33
- i += 1
34
- }
35
- } else if (ch === '?') {
36
- // Single char, not slash
37
- regexStr += '[^/]'
38
- i += 1
39
- } else {
40
- // Escape regex metacharacters in literal segments
41
- regexStr += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&')
42
- i += 1
43
- }
44
- }
45
- regexStr += '$'
46
-
47
- return new RegExp(regexStr).test(path)
48
- }
49
-
50
- /**
51
- * Find the first matching policy for a given path.
52
- * Returns null if no policy matches.
53
- */
54
- export function matchPolicy(policies: RoutePolicy[], path: string): RoutePolicy | null {
55
- for (const policy of policies) {
56
- if (matchGlob(policy.path, path)) {
57
- return policy
58
- }
59
- }
60
- return null
61
- }
62
-
63
- /**
64
- * Check whether a sender is permitted by a policy.
65
- * If allowedSenders is not set, all senders are allowed.
66
- */
67
- export function isAllowedSender(policy: RoutePolicy, sender: string): boolean {
68
- if (!policy.allowedSenders || policy.allowedSenders.length === 0) {
69
- return true
70
- }
71
- return policy.allowedSenders.includes(sender)
72
- }
@@ -1,49 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { loadRuntimePolicy, parseRuntimePolicyJson, validateRuntimePolicy } from './runtimePolicy'
3
-
4
- describe('runtime policy loader', () => {
5
- it('parses valid policy JSON', () => {
6
- const policy = parseRuntimePolicyJson(
7
- JSON.stringify({
8
- version: '1.0',
9
- name: 'gluv-runtime-policy',
10
- status: 'active',
11
- }),
12
- )
13
-
14
- expect(policy.version).toBe('1.0')
15
- expect(policy.name).toBe('gluv-runtime-policy')
16
- expect(policy.status).toBe('active')
17
- })
18
-
19
- it('rejects invalid JSON', () => {
20
- expect(() => parseRuntimePolicyJson('{')).toThrow(/Invalid runtime policy JSON/i)
21
- })
22
-
23
- it('rejects invalid root object', () => {
24
- expect(() => validateRuntimePolicy('not-an-object')).toThrow(/root must be an object/i)
25
- })
26
-
27
- it('rejects missing required fields', () => {
28
- expect(() => validateRuntimePolicy({ name: 'x', status: 'active' })).toThrow(/version/i)
29
- expect(() => validateRuntimePolicy({ version: '1.0', status: 'active' })).toThrow(/name/i)
30
- expect(() => validateRuntimePolicy({ version: '1.0', name: 'x' })).toThrow(/status/i)
31
- })
32
-
33
- it('loads policy using injected reader and path', async () => {
34
- const policy = await loadRuntimePolicy({
35
- path: '/virtual/AI_RUNTIME_POLICY.json',
36
- readTextFile: async (filePath) => {
37
- expect(filePath).toBe('/virtual/AI_RUNTIME_POLICY.json')
38
- return JSON.stringify({
39
- version: '1.0',
40
- name: 'gluv-runtime-policy',
41
- status: 'active',
42
- owner: 'platform-ai-ops',
43
- })
44
- },
45
- })
46
-
47
- expect(policy.owner).toBe('platform-ai-ops')
48
- })
49
- })
@@ -1,81 +0,0 @@
1
- export interface RuntimePolicy {
2
- version: string
3
- name: string
4
- status: string
5
- owner?: string
6
- updated_at?: string
7
- hard_invariants?: Record<string, unknown>
8
- slo_defaults?: Record<string, unknown>
9
- mode_selection?: Record<string, unknown>
10
- guardrails?: Record<string, unknown>
11
- tuning?: Record<string, unknown>
12
- adaptive_flow?: Record<string, unknown>
13
- benchmark_policy?: Record<string, unknown>
14
- runtime_decision_tree?: unknown[]
15
- operator_handoff_required_fields?: string[]
16
- [key: string]: unknown
17
- }
18
-
19
- export interface LoadRuntimePolicyOptions {
20
- path?: string
21
- readTextFile?: (filePath: string) => Promise<string>
22
- }
23
-
24
- function isRecord(value: unknown): value is Record<string, unknown> {
25
- return typeof value === 'object' && value !== null && !Array.isArray(value)
26
- }
27
-
28
- function asNonEmptyString(value: unknown, field: string): string {
29
- if (typeof value !== 'string' || value.trim().length === 0) {
30
- throw new Error(`Invalid runtime policy: '${field}' must be a non-empty string`)
31
- }
32
- return value
33
- }
34
-
35
- export function validateRuntimePolicy(value: unknown): RuntimePolicy {
36
- if (!isRecord(value)) {
37
- throw new Error('Invalid runtime policy: root must be an object')
38
- }
39
-
40
- const version = asNonEmptyString(value.version, 'version')
41
- const name = asNonEmptyString(value.name, 'name')
42
- const status = asNonEmptyString(value.status, 'status')
43
-
44
- return {
45
- ...value,
46
- version,
47
- name,
48
- status,
49
- }
50
- }
51
-
52
- export function parseRuntimePolicyJson(jsonText: string): RuntimePolicy {
53
- let parsed: unknown
54
- try {
55
- parsed = JSON.parse(jsonText)
56
- } catch (error) {
57
- const message = error instanceof Error ? error.message : String(error)
58
- throw new Error(`Invalid runtime policy JSON: ${message}`)
59
- }
60
- return validateRuntimePolicy(parsed)
61
- }
62
-
63
- async function defaultReadTextFile(filePath: string): Promise<string> {
64
- if (typeof fetch === 'function') {
65
- const response = await fetch(filePath)
66
- if (!response.ok) {
67
- throw new Error(`Failed to fetch runtime policy at '${filePath}': ${response.status} ${response.statusText}`)
68
- }
69
- return response.text()
70
- }
71
- throw new Error(
72
- "No default runtime file reader available in this environment. Provide 'readTextFile' in loadRuntimePolicy(options).",
73
- )
74
- }
75
-
76
- export async function loadRuntimePolicy(options: LoadRuntimePolicyOptions = {}): Promise<RuntimePolicy> {
77
- const filePath = options.path ?? 'AI_RUNTIME_POLICY.json'
78
- const readTextFile = options.readTextFile ?? defaultReadTextFile
79
- const text = await readTextFile(filePath)
80
- return parseRuntimePolicyJson(text)
81
- }
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { RuntimePolicyManager } from './runtimePolicyManager'
3
-
4
- describe('runtime policy manager', () => {
5
- it('loads initial policy and exposes snapshot', async () => {
6
- const manager = new RuntimePolicyManager({
7
- path: '/virtual/AI_RUNTIME_POLICY.json',
8
- readTextFile: async () =>
9
- JSON.stringify({
10
- version: '1.0',
11
- name: 'gluv-runtime-policy',
12
- status: 'active',
13
- hard_invariants: {},
14
- slo_defaults: {},
15
- tuning: {
16
- batch_size: { low_traffic: [4, 8], medium_traffic: [8, 32], high_traffic: [32, 64] },
17
- inflight_cap: { low_traffic: [16, 64], medium_traffic: [64, 256], high_traffic: [256, 1024] },
18
- retry_backoff_ms: { low_traffic: [1, 2], medium_traffic: [2, 8], high_traffic: [4, 16] },
19
- max_retry_attempts: 3,
20
- retriable_status_codes: [503],
21
- },
22
- }),
23
- })
24
-
25
- const loaded = await manager.loadInitial()
26
- expect(loaded.policy.name).toBe('gluv-runtime-policy')
27
- expect(manager.current().policy.version).toBe('1.0')
28
- })
29
- })
@@ -1,50 +0,0 @@
1
- import { bootstrapRuntimePolicyEnforcer, type BootstrapRuntimePolicyEnforcerOptions, type PolicyEnforcer } from './policyEnforcer'
2
- import type { RuntimePolicy } from './runtimePolicy'
3
-
4
- export interface RuntimePolicySnapshot {
5
- policy: RuntimePolicy
6
- enforcer: PolicyEnforcer
7
- loadedAtMs: number
8
- }
9
-
10
- export class RuntimePolicyManager {
11
- private snapshot: RuntimePolicySnapshot | null = null
12
- private readonly options: BootstrapRuntimePolicyEnforcerOptions
13
-
14
- constructor(options: BootstrapRuntimePolicyEnforcerOptions = {}) {
15
- this.options = options
16
- }
17
-
18
- async loadInitial(): Promise<RuntimePolicySnapshot> {
19
- const loaded = await bootstrapRuntimePolicyEnforcer(this.options)
20
- this.snapshot = {
21
- ...loaded,
22
- loadedAtMs: Date.now(),
23
- }
24
- return this.snapshot
25
- }
26
-
27
- current(): RuntimePolicySnapshot {
28
- if (!this.snapshot) {
29
- throw new Error('Runtime policy not loaded. Call loadInitial() first.')
30
- }
31
- return this.snapshot
32
- }
33
-
34
- async refresh(): Promise<RuntimePolicySnapshot> {
35
- const previous = this.snapshot
36
- try {
37
- const loaded = await bootstrapRuntimePolicyEnforcer(this.options)
38
- this.snapshot = {
39
- ...loaded,
40
- loadedAtMs: Date.now(),
41
- }
42
- return this.snapshot
43
- } catch (error) {
44
- if (previous) {
45
- return previous
46
- }
47
- throw error
48
- }
49
- }
50
- }
@@ -1,43 +0,0 @@
1
- import type { RuntimeMode } from './policyEnforcer'
2
-
3
- export type RuntimeEnvironment = 'dev' | 'staging' | 'prod'
4
-
5
- export interface RuntimeTuningPreset {
6
- environment: RuntimeEnvironment
7
- mode: RuntimeMode
8
- poolSize: number
9
- batchSize: number
10
- inflightCap: number
11
- retryMaxAttempts: number
12
- }
13
-
14
- const PRESETS: Record<RuntimeEnvironment, RuntimeTuningPreset> = {
15
- dev: {
16
- environment: 'dev',
17
- mode: 'ws-batch',
18
- poolSize: 1,
19
- batchSize: 8,
20
- inflightCap: 64,
21
- retryMaxAttempts: 2,
22
- },
23
- staging: {
24
- environment: 'staging',
25
- mode: 'http-binary-batch',
26
- poolSize: 2,
27
- batchSize: 32,
28
- inflightCap: 256,
29
- retryMaxAttempts: 3,
30
- },
31
- prod: {
32
- environment: 'prod',
33
- mode: 'http-binary-batch',
34
- poolSize: 4,
35
- batchSize: 64,
36
- inflightCap: 1024,
37
- retryMaxAttempts: 3,
38
- },
39
- }
40
-
41
- export function getRuntimeTuningPreset(environment: RuntimeEnvironment): RuntimeTuningPreset {
42
- return PRESETS[environment]
43
- }
@@ -1,111 +0,0 @@
1
- import { describe, it, expect, beforeAll } from 'vitest'
2
- import { signResponse, verifyResponse, RESPONSE_HEADER } from './signedResponse'
3
- import { generateEd25519KeypairBase64Url } from './protocol'
4
-
5
- let keys: { publicKey: string; privateKey: string }
6
- let wrongKeys: { publicKey: string; privateKey: string }
7
-
8
- beforeAll(async () => {
9
- keys = await generateEd25519KeypairBase64Url()
10
- wrongKeys = await generateEd25519KeypairBase64Url()
11
- })
12
-
13
- describe('signedResponse', () => {
14
- it('round-trip: signs and verifies correctly', async () => {
15
- const body = 'hello world'
16
- const { headers } = await signResponse(body, {
17
- privateKey: keys.privateKey,
18
- sender: 'gateway-agent',
19
- })
20
-
21
- const result = await verifyResponse(body, headers, { publicKey: keys.publicKey })
22
- expect(result.ok).toBe(true)
23
- expect(result.envelope?.body.intent).toBe('RESULT')
24
- expect(result.envelope?.body.content).toBe(body)
25
- })
26
-
27
- it('fails when body does not match envelope content', async () => {
28
- const body = 'original body'
29
- const { headers } = await signResponse(body, {
30
- privateKey: keys.privateKey,
31
- sender: 'gateway-agent',
32
- })
33
-
34
- const result = await verifyResponse('tampered body', headers, { publicKey: keys.publicKey })
35
- expect(result.ok).toBe(false)
36
- expect(result.reason).toBe('content-mismatch')
37
- })
38
-
39
- it('fails with wrong public key', async () => {
40
- const body = 'some content'
41
- const { headers } = await signResponse(body, {
42
- privateKey: keys.privateKey,
43
- sender: 'gateway-agent',
44
- })
45
-
46
- const result = await verifyResponse(body, headers, { publicKey: wrongKeys.publicKey })
47
- expect(result.ok).toBe(false)
48
- expect(result.reason).toBe('invalid-signature')
49
- })
50
-
51
- it('fails when header is missing', async () => {
52
- const result = await verifyResponse('body', {}, { publicKey: keys.publicKey })
53
- expect(result.ok).toBe(false)
54
- expect(result.reason).toBe('missing-header')
55
- })
56
-
57
- it('fails when envelope TTL is expired', async () => {
58
- // Sign with 1ms ttl — immediately expired
59
- const body = 'ttl-test'
60
- const { headers } = await signResponse(body, {
61
- privateKey: keys.privateKey,
62
- sender: 'gateway-agent',
63
- ttlMs: 1,
64
- })
65
-
66
- // Wait a tick to ensure expiry
67
- await new Promise(r => setTimeout(r, 10))
68
-
69
- const result = await verifyResponse(body, headers, { publicKey: keys.publicKey })
70
- expect(result.ok).toBe(false)
71
- expect(result.reason).toBe('ttl-expired')
72
- })
73
-
74
- it('fails when maxAgeMs exceeded', async () => {
75
- const body = 'max-age-test'
76
- const { headers } = await signResponse(body, {
77
- privateKey: keys.privateKey,
78
- sender: 'gateway-agent',
79
- ttlMs: 60_000, // long ttl
80
- })
81
-
82
- // Wait to ensure age exceeds maxAgeMs
83
- await new Promise(r => setTimeout(r, 10))
84
-
85
- const result = await verifyResponse(body, headers, {
86
- publicKey: keys.publicKey,
87
- maxAgeMs: 1, // tiny window
88
- })
89
- expect(result.ok).toBe(false)
90
- expect(result.reason).toBe('ttl-expired')
91
- })
92
-
93
- it('passes correlationId and recipient through envelope', async () => {
94
- const body = 'corr-test'
95
- const { headers } = await signResponse(body, {
96
- privateKey: keys.privateKey,
97
- sender: 'gateway-agent',
98
- recipient: 'client-agent',
99
- correlationId: 'req-123',
100
- })
101
-
102
- const result = await verifyResponse(body, headers, { publicKey: keys.publicKey })
103
- expect(result.ok).toBe(true)
104
- expect(result.envelope?.body.correlationId).toBe('req-123')
105
- expect(result.envelope?.header.recipient).toBe('client-agent')
106
- })
107
-
108
- it('RESPONSE_HEADER constant is x-7h3-response', () => {
109
- expect(RESPONSE_HEADER).toBe('x-7h3-response')
110
- })
111
- })