@gethelio/proxy 0.11.1 → 0.13.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/dist/index.d.ts CHANGED
@@ -6,10 +6,18 @@ import { KnownBlock } from '@slack/web-api';
6
6
 
7
7
  declare const VERSION: string;
8
8
 
9
+ declare const sessionIdentitySourceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
10
+ source: z.ZodLiteral<"header">;
11
+ name: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
12
+ }, z.core.$strict>, z.ZodObject<{
13
+ source: z.ZodLiteral<"meta">;
14
+ }, z.core.$strict>, z.ZodObject<{
15
+ source: z.ZodLiteral<"legacy_header">;
16
+ }, z.core.$strict>], "source">;
9
17
  declare const policiesSchema: z.ZodObject<{
10
18
  default: z.ZodDefault<z.ZodEnum<{
11
- allow: "allow";
12
19
  deny: "deny";
20
+ allow: "allow";
13
21
  }>>;
14
22
  flag_destructive: z.ZodOptional<z.ZodEnum<{
15
23
  require_approval: "require_approval";
@@ -43,10 +51,11 @@ declare const policiesSchema: z.ZodObject<{
43
51
  contains: z.ZodOptional<z.ZodString>;
44
52
  regex: z.ZodOptional<z.ZodString>;
45
53
  }, z.core.$strict>]>>>;
54
+ upstreams: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
55
  }, z.core.$strict>;
47
56
  action: z.ZodEnum<{
48
- allow: "allow";
49
57
  deny: "deny";
58
+ allow: "allow";
50
59
  require_approval: "require_approval";
51
60
  rate_limit: "rate_limit";
52
61
  spend_limit: "spend_limit";
@@ -92,8 +101,8 @@ declare const policiesSchema: z.ZodObject<{
92
101
  }, z.core.$strict>>>;
93
102
  install: z.ZodOptional<z.ZodObject<{
94
103
  default: z.ZodDefault<z.ZodEnum<{
95
- allow: "allow";
96
104
  deny: "deny";
105
+ allow: "allow";
97
106
  }>>;
98
107
  rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
99
108
  name: z.ZodOptional<z.ZodString>;
@@ -122,6 +131,11 @@ declare const policiesSchema: z.ZodObject<{
122
131
  log: "log";
123
132
  block: "block";
124
133
  }>>;
134
+ tool_revalidation: z.ZodOptional<z.ZodObject<{
135
+ enabled: z.ZodDefault<z.ZodBoolean>;
136
+ interval: z.ZodDefault<z.ZodString>;
137
+ max_advertised_ttl: z.ZodOptional<z.ZodString>;
138
+ }, z.core.$strict>>;
125
139
  hot_reload: z.ZodOptional<z.ZodBoolean>;
126
140
  }, z.core.$strict>;
127
141
  declare const budgetSchema: z.ZodObject<{
@@ -158,6 +172,7 @@ declare const budgetSchema: z.ZodObject<{
158
172
  contains: z.ZodOptional<z.ZodString>;
159
173
  regex: z.ZodOptional<z.ZodString>;
160
174
  }, z.core.$strict>>>;
175
+ upstreams: z.ZodOptional<z.ZodArray<z.ZodString>>;
161
176
  }, z.core.$strict>;
162
177
  field: z.ZodString;
163
178
  }, z.core.$strict>>>;
@@ -177,16 +192,247 @@ declare const approvalChannelSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
177
192
  type: z.ZodLiteral<"dashboard">;
178
193
  name: z.ZodOptional<z.ZodString>;
179
194
  }, z.core.$strict>], "type">;
180
- /** Zod schema for the complete `helio.yaml` configuration file. */
181
- declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
195
+ declare const singularConfigSchema: z.ZodObject<{
196
+ listen: z.ZodPrefault<z.ZodObject<{
197
+ port: z.ZodDefault<z.ZodNumber>;
198
+ host: z.ZodDefault<z.ZodString>;
199
+ allowed_origins: z.ZodDefault<z.ZodArray<z.ZodString>>;
200
+ }, z.core.$strict>>;
201
+ environment: z.ZodOptional<z.ZodString>;
202
+ session: z.ZodPrefault<z.ZodObject<{
203
+ identity: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
204
+ source: z.ZodLiteral<"header">;
205
+ name: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
206
+ }, z.core.$strict>, z.ZodObject<{
207
+ source: z.ZodLiteral<"meta">;
208
+ }, z.core.$strict>, z.ZodObject<{
209
+ source: z.ZodLiteral<"legacy_header">;
210
+ }, z.core.$strict>], "source">>>;
211
+ on_unresolved: z.ZodDefault<z.ZodEnum<{
212
+ deny: "deny";
213
+ anonymous: "anonymous";
214
+ }>>;
215
+ }, z.core.$strict>>;
216
+ policies: z.ZodPrefault<z.ZodObject<{
217
+ default: z.ZodDefault<z.ZodEnum<{
218
+ deny: "deny";
219
+ allow: "allow";
220
+ }>>;
221
+ flag_destructive: z.ZodOptional<z.ZodEnum<{
222
+ require_approval: "require_approval";
223
+ log: "log";
224
+ }>>;
225
+ dry_run: z.ZodDefault<z.ZodBoolean>;
226
+ rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
227
+ name: z.ZodOptional<z.ZodString>;
228
+ match: z.ZodObject<{
229
+ tool: z.ZodOptional<z.ZodString>;
230
+ annotations: z.ZodOptional<z.ZodObject<{
231
+ readOnlyHint: z.ZodOptional<z.ZodBoolean>;
232
+ destructiveHint: z.ZodOptional<z.ZodBoolean>;
233
+ idempotentHint: z.ZodOptional<z.ZodBoolean>;
234
+ openWorldHint: z.ZodOptional<z.ZodBoolean>;
235
+ }, z.core.$strict>>;
236
+ input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
237
+ eq: z.ZodOptional<z.ZodUnknown>;
238
+ neq: z.ZodOptional<z.ZodUnknown>;
239
+ gt: z.ZodOptional<z.ZodNumber>;
240
+ gte: z.ZodOptional<z.ZodNumber>;
241
+ lt: z.ZodOptional<z.ZodNumber>;
242
+ lte: z.ZodOptional<z.ZodNumber>;
243
+ contains: z.ZodOptional<z.ZodString>;
244
+ regex: z.ZodOptional<z.ZodString>;
245
+ }, z.core.$strict>>>;
246
+ environment: z.ZodOptional<z.ZodString>;
247
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
248
+ eq: z.ZodOptional<z.ZodString>;
249
+ neq: z.ZodOptional<z.ZodString>;
250
+ contains: z.ZodOptional<z.ZodString>;
251
+ regex: z.ZodOptional<z.ZodString>;
252
+ }, z.core.$strict>]>>>;
253
+ upstreams: z.ZodOptional<z.ZodArray<z.ZodString>>;
254
+ }, z.core.$strict>;
255
+ action: z.ZodEnum<{
256
+ deny: "deny";
257
+ allow: "allow";
258
+ require_approval: "require_approval";
259
+ rate_limit: "rate_limit";
260
+ spend_limit: "spend_limit";
261
+ dry_run: "dry_run";
262
+ }>;
263
+ approval: z.ZodOptional<z.ZodObject<{
264
+ channel: z.ZodString;
265
+ timeout: z.ZodOptional<z.ZodString>;
266
+ delegates: z.ZodOptional<z.ZodArray<z.ZodString>>;
267
+ escalation_after: z.ZodOptional<z.ZodString>;
268
+ }, z.core.$strict>>;
269
+ evidence: z.ZodOptional<z.ZodObject<{
270
+ requires: z.ZodArray<z.ZodString>;
271
+ }, z.core.$strict>>;
272
+ requires: z.ZodOptional<z.ZodArray<z.ZodString>>;
273
+ requires_success: z.ZodOptional<z.ZodBoolean>;
274
+ limits: z.ZodOptional<z.ZodObject<{
275
+ max_calls: z.ZodOptional<z.ZodNumber>;
276
+ window: z.ZodOptional<z.ZodString>;
277
+ key: z.ZodOptional<z.ZodEnum<{
278
+ tool: "tool";
279
+ agent: "agent";
280
+ session: "session";
281
+ sender_id: "sender_id";
282
+ }>>;
283
+ max_spend: z.ZodOptional<z.ZodObject<{
284
+ field: z.ZodString;
285
+ limit: z.ZodNumber;
286
+ currency: z.ZodString;
287
+ window: z.ZodString;
288
+ key: z.ZodOptional<z.ZodEnum<{
289
+ tool: "tool";
290
+ agent: "agent";
291
+ session: "session";
292
+ sender_id: "sender_id";
293
+ }>>;
294
+ }, z.core.$strict>>;
295
+ }, z.core.$strict>>;
296
+ feedback: z.ZodOptional<z.ZodObject<{
297
+ message: z.ZodString;
298
+ suggestion: z.ZodOptional<z.ZodString>;
299
+ }, z.core.$strict>>;
300
+ }, z.core.$strict>>>;
301
+ install: z.ZodOptional<z.ZodObject<{
302
+ default: z.ZodDefault<z.ZodEnum<{
303
+ deny: "deny";
304
+ allow: "allow";
305
+ }>>;
306
+ rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
307
+ name: z.ZodOptional<z.ZodString>;
308
+ match: z.ZodObject<{
309
+ name: z.ZodOptional<z.ZodString>;
310
+ source: z.ZodOptional<z.ZodString>;
311
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
312
+ eq: z.ZodOptional<z.ZodString>;
313
+ neq: z.ZodOptional<z.ZodString>;
314
+ contains: z.ZodOptional<z.ZodString>;
315
+ regex: z.ZodOptional<z.ZodString>;
316
+ }, z.core.$strict>]>>>;
317
+ }, z.core.$strict>;
318
+ action: z.ZodEnum<{
319
+ allow: "allow";
320
+ deny_install: "deny_install";
321
+ }>;
322
+ feedback: z.ZodOptional<z.ZodObject<{
323
+ message: z.ZodString;
324
+ suggestion: z.ZodOptional<z.ZodString>;
325
+ }, z.core.$strict>>;
326
+ }, z.core.$strict>>>;
327
+ }, z.core.$strict>>;
328
+ on_tool_drift: z.ZodOptional<z.ZodEnum<{
329
+ require_approval: "require_approval";
330
+ log: "log";
331
+ block: "block";
332
+ }>>;
333
+ tool_revalidation: z.ZodOptional<z.ZodObject<{
334
+ enabled: z.ZodDefault<z.ZodBoolean>;
335
+ interval: z.ZodDefault<z.ZodString>;
336
+ max_advertised_ttl: z.ZodOptional<z.ZodString>;
337
+ }, z.core.$strict>>;
338
+ hot_reload: z.ZodOptional<z.ZodBoolean>;
339
+ }, z.core.$strict>>;
340
+ budgets: z.ZodDefault<z.ZodArray<z.ZodObject<{
341
+ name: z.ZodString;
342
+ limit: z.ZodNumber;
343
+ currency: z.ZodString;
344
+ window: z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"session">]>;
345
+ key: z.ZodDefault<z.ZodEnum<{
346
+ session: "session";
347
+ sender_id: "sender_id";
348
+ global: "global";
349
+ }>>;
350
+ on_exceed: z.ZodDefault<z.ZodEnum<{
351
+ deny: "deny";
352
+ require_approval: "require_approval";
353
+ }>>;
354
+ approval: z.ZodOptional<z.ZodObject<{
355
+ channel: z.ZodString;
356
+ timeout: z.ZodOptional<z.ZodString>;
357
+ delegates: z.ZodOptional<z.ZodArray<z.ZodString>>;
358
+ escalation_after: z.ZodOptional<z.ZodString>;
359
+ }, z.core.$strict>>;
360
+ idle_ttl: z.ZodOptional<z.ZodString>;
361
+ contributors: z.ZodArray<z.ZodPipe<z.ZodUnknown, z.ZodObject<{
362
+ match: z.ZodObject<{
363
+ tool: z.ZodString;
364
+ input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
365
+ eq: z.ZodOptional<z.ZodUnknown>;
366
+ neq: z.ZodOptional<z.ZodUnknown>;
367
+ gt: z.ZodOptional<z.ZodNumber>;
368
+ gte: z.ZodOptional<z.ZodNumber>;
369
+ lt: z.ZodOptional<z.ZodNumber>;
370
+ lte: z.ZodOptional<z.ZodNumber>;
371
+ contains: z.ZodOptional<z.ZodString>;
372
+ regex: z.ZodOptional<z.ZodString>;
373
+ }, z.core.$strict>>>;
374
+ upstreams: z.ZodOptional<z.ZodArray<z.ZodString>>;
375
+ }, z.core.$strict>;
376
+ field: z.ZodString;
377
+ }, z.core.$strict>>>;
378
+ }, z.core.$strict>>>;
379
+ approval: z.ZodPrefault<z.ZodObject<{
380
+ timeout: z.ZodDefault<z.ZodString>;
381
+ default_on_timeout: z.ZodDefault<z.ZodEnum<{
382
+ deny: "deny";
383
+ allow: "allow";
384
+ }>>;
385
+ channels: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
386
+ type: z.ZodLiteral<"slack">;
387
+ name: z.ZodOptional<z.ZodString>;
388
+ bot_token: z.ZodString;
389
+ signing_secret: z.ZodString;
390
+ channel: z.ZodString;
391
+ }, z.core.$strict>, z.ZodObject<{
392
+ type: z.ZodLiteral<"webhook">;
393
+ name: z.ZodOptional<z.ZodString>;
394
+ url: z.ZodString;
395
+ secret: z.ZodOptional<z.ZodString>;
396
+ }, z.core.$strict>, z.ZodObject<{
397
+ type: z.ZodLiteral<"dashboard">;
398
+ name: z.ZodOptional<z.ZodString>;
399
+ }, z.core.$strict>], "type">>>;
400
+ }, z.core.$strict>>;
401
+ audit: z.ZodPrefault<z.ZodObject<{
402
+ storage: z.ZodDefault<z.ZodEnum<{
403
+ sqlite: "sqlite";
404
+ }>>;
405
+ path: z.ZodDefault<z.ZodString>;
406
+ retention: z.ZodDefault<z.ZodString>;
407
+ include_responses: z.ZodDefault<z.ZodBoolean>;
408
+ }, z.core.$strict>>;
409
+ dashboard: z.ZodPrefault<z.ZodObject<{
410
+ enabled: z.ZodDefault<z.ZodBoolean>;
411
+ port: z.ZodDefault<z.ZodNumber>;
412
+ host: z.ZodDefault<z.ZodString>;
413
+ api_secret: z.ZodOptional<z.ZodString>;
414
+ allow_open_mode: z.ZodDefault<z.ZodBoolean>;
415
+ sse_heartbeat_interval: z.ZodDefault<z.ZodString>;
416
+ }, z.core.$strict>>;
417
+ sdk: z.ZodPrefault<z.ZodObject<{
418
+ enabled: z.ZodDefault<z.ZodBoolean>;
419
+ port: z.ZodDefault<z.ZodNumber>;
420
+ host: z.ZodDefault<z.ZodString>;
421
+ evaluation_ttl: z.ZodDefault<z.ZodString>;
422
+ }, z.core.$strict>>;
182
423
  version: z.ZodLiteral<"1">;
183
424
  upstream: z.ZodObject<{
184
- url: z.ZodString;
425
+ url: z.ZodOptional<z.ZodString>;
185
426
  transport: z.ZodDefault<z.ZodEnum<{
186
427
  "streamable-http": "streamable-http";
187
428
  sse: "sse";
188
429
  stdio: "stdio";
189
430
  }>>;
431
+ protocol_version: z.ZodDefault<z.ZodEnum<{
432
+ auto: "auto";
433
+ "2025-06-18": "2025-06-18";
434
+ "2026-07-28": "2026-07-28";
435
+ }>>;
190
436
  command: z.ZodOptional<z.ZodString>;
191
437
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
192
438
  connect_timeout: z.ZodDefault<z.ZodString>;
@@ -194,15 +440,32 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
194
440
  forward_headers: z.ZodDefault<z.ZodArray<z.ZodString>>;
195
441
  headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
196
442
  }, z.core.$strict>;
443
+ }, z.core.$strict>;
444
+ declare const namedConfigSchema: z.ZodObject<{
197
445
  listen: z.ZodPrefault<z.ZodObject<{
198
446
  port: z.ZodDefault<z.ZodNumber>;
199
447
  host: z.ZodDefault<z.ZodString>;
448
+ allowed_origins: z.ZodDefault<z.ZodArray<z.ZodString>>;
200
449
  }, z.core.$strict>>;
201
450
  environment: z.ZodOptional<z.ZodString>;
451
+ session: z.ZodPrefault<z.ZodObject<{
452
+ identity: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
453
+ source: z.ZodLiteral<"header">;
454
+ name: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
455
+ }, z.core.$strict>, z.ZodObject<{
456
+ source: z.ZodLiteral<"meta">;
457
+ }, z.core.$strict>, z.ZodObject<{
458
+ source: z.ZodLiteral<"legacy_header">;
459
+ }, z.core.$strict>], "source">>>;
460
+ on_unresolved: z.ZodDefault<z.ZodEnum<{
461
+ deny: "deny";
462
+ anonymous: "anonymous";
463
+ }>>;
464
+ }, z.core.$strict>>;
202
465
  policies: z.ZodPrefault<z.ZodObject<{
203
466
  default: z.ZodDefault<z.ZodEnum<{
204
- allow: "allow";
205
467
  deny: "deny";
468
+ allow: "allow";
206
469
  }>>;
207
470
  flag_destructive: z.ZodOptional<z.ZodEnum<{
208
471
  require_approval: "require_approval";
@@ -236,10 +499,11 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
236
499
  contains: z.ZodOptional<z.ZodString>;
237
500
  regex: z.ZodOptional<z.ZodString>;
238
501
  }, z.core.$strict>]>>>;
502
+ upstreams: z.ZodOptional<z.ZodArray<z.ZodString>>;
239
503
  }, z.core.$strict>;
240
504
  action: z.ZodEnum<{
241
- allow: "allow";
242
505
  deny: "deny";
506
+ allow: "allow";
243
507
  require_approval: "require_approval";
244
508
  rate_limit: "rate_limit";
245
509
  spend_limit: "spend_limit";
@@ -285,8 +549,8 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
285
549
  }, z.core.$strict>>>;
286
550
  install: z.ZodOptional<z.ZodObject<{
287
551
  default: z.ZodDefault<z.ZodEnum<{
288
- allow: "allow";
289
552
  deny: "deny";
553
+ allow: "allow";
290
554
  }>>;
291
555
  rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
292
556
  name: z.ZodOptional<z.ZodString>;
@@ -315,6 +579,11 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
315
579
  log: "log";
316
580
  block: "block";
317
581
  }>>;
582
+ tool_revalidation: z.ZodOptional<z.ZodObject<{
583
+ enabled: z.ZodDefault<z.ZodBoolean>;
584
+ interval: z.ZodDefault<z.ZodString>;
585
+ max_advertised_ttl: z.ZodOptional<z.ZodString>;
586
+ }, z.core.$strict>>;
318
587
  hot_reload: z.ZodOptional<z.ZodBoolean>;
319
588
  }, z.core.$strict>>;
320
589
  budgets: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -351,6 +620,7 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
351
620
  contains: z.ZodOptional<z.ZodString>;
352
621
  regex: z.ZodOptional<z.ZodString>;
353
622
  }, z.core.$strict>>>;
623
+ upstreams: z.ZodOptional<z.ZodArray<z.ZodString>>;
354
624
  }, z.core.$strict>;
355
625
  field: z.ZodString;
356
626
  }, z.core.$strict>>>;
@@ -358,8 +628,8 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
358
628
  approval: z.ZodPrefault<z.ZodObject<{
359
629
  timeout: z.ZodDefault<z.ZodString>;
360
630
  default_on_timeout: z.ZodDefault<z.ZodEnum<{
361
- allow: "allow";
362
631
  deny: "deny";
632
+ allow: "allow";
363
633
  }>>;
364
634
  channels: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
365
635
  type: z.ZodLiteral<"slack">;
@@ -399,13 +669,44 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
399
669
  host: z.ZodDefault<z.ZodString>;
400
670
  evaluation_ttl: z.ZodDefault<z.ZodString>;
401
671
  }, z.core.$strict>>;
402
- }, z.core.$strict>>;
403
- /** Fully validated and defaulted Helio configuration. */
404
- type HelioConfig = z.infer<typeof helioConfigSchema>;
672
+ version: z.ZodLiteral<"1">;
673
+ upstreams: z.ZodArray<z.ZodObject<{
674
+ url: z.ZodOptional<z.ZodString>;
675
+ transport: z.ZodDefault<z.ZodEnum<{
676
+ "streamable-http": "streamable-http";
677
+ sse: "sse";
678
+ stdio: "stdio";
679
+ }>>;
680
+ protocol_version: z.ZodDefault<z.ZodEnum<{
681
+ auto: "auto";
682
+ "2025-06-18": "2025-06-18";
683
+ "2026-07-28": "2026-07-28";
684
+ }>>;
685
+ command: z.ZodOptional<z.ZodString>;
686
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
687
+ connect_timeout: z.ZodDefault<z.ZodString>;
688
+ request_timeout: z.ZodDefault<z.ZodString>;
689
+ forward_headers: z.ZodDefault<z.ZodArray<z.ZodString>>;
690
+ headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
691
+ name: z.ZodString;
692
+ }, z.core.$strict>>;
693
+ }, z.core.$strict>;
694
+ /** A fully validated and defaulted singular-mode (`upstream:`) configuration. */
695
+ type SingularHelioConfig = z.output<typeof singularConfigSchema>;
696
+ /** A fully validated and defaulted named-mode (`upstreams:`) configuration. */
697
+ type NamedHelioConfig = z.output<typeof namedConfigSchema>;
698
+ /** Fully validated and defaulted Helio configuration — one of the two mode arms. */
699
+ type HelioConfig = SingularHelioConfig | NamedHelioConfig;
700
+ /** Narrow a parsed config to the singular-mode (`upstream:`) arm. */
701
+ declare function isSingularConfig(config: HelioConfig): config is SingularHelioConfig;
702
+ /** Narrow a parsed config to the named-mode (`upstreams:`) arm. */
703
+ declare function isNamedConfig(config: HelioConfig): config is NamedHelioConfig;
405
704
  /** An approval channel configuration (slack, webhook, or dashboard). */
406
705
  type ApprovalChannel$1 = z.infer<typeof approvalChannelSchema>;
407
706
  /** The policies section of the config. */
408
707
  type PoliciesConfig = z.infer<typeof policiesSchema>;
708
+ /** A single identity source from `session.identity`. */
709
+ type SessionIdentitySource = z.infer<typeof sessionIdentitySourceSchema>;
409
710
  /** A single named budget from the `budgets` array (issue #14). */
410
711
  type BudgetConfig = z.infer<typeof budgetSchema>;
411
712
  /** The `budgets` section of the config. */
@@ -490,6 +791,8 @@ interface CompiledMatch {
490
791
  readonly environment?: string;
491
792
  /** Flattened list of metadata conditions (one entry per key+operator pair). */
492
793
  readonly metadata?: readonly MetadataCondition[];
794
+ /** Configured upstream names the rule is scoped to, OR within the list (issue #293). */
795
+ readonly upstreams?: readonly string[];
493
796
  }
494
797
  /** Compiled approval configuration with durations as milliseconds. */
495
798
  interface CompiledApproval {
@@ -513,6 +816,12 @@ interface CompiledLimits {
513
816
  readonly key?: 'tool' | 'agent' | 'session' | 'sender_id';
514
817
  readonly maxSpend?: CompiledSpendLimit;
515
818
  }
819
+ /** Compiled tool revalidation settings with durations as milliseconds. */
820
+ interface CompiledToolRevalidation {
821
+ readonly enabled: boolean;
822
+ readonly intervalMs: number;
823
+ readonly maxAdvertisedTtlMs: number;
824
+ }
516
825
  /** Policy action types. */
517
826
  type PolicyAction = 'allow' | 'deny' | 'require_approval' | 'rate_limit' | 'spend_limit' | 'dry_run';
518
827
  /** A fully compiled policy rule ready for engine evaluation. */
@@ -576,6 +885,8 @@ interface CompiledPolicy {
576
885
  * at the use site — conservative by default.
577
886
  */
578
887
  readonly onToolDrift?: 'block' | 'require_approval' | 'log';
888
+ /** Always set by compilePolicies; absent only in hand-built fixtures (treated as disabled). */
889
+ readonly toolRevalidation?: CompiledToolRevalidation;
579
890
  readonly rules: readonly CompiledPolicyRule[];
580
891
  /** Install-time policy (issue #13 — deny_install). Undefined ⇒ observational. */
581
892
  readonly install?: CompiledInstallPolicy;
@@ -625,6 +936,12 @@ interface MatchContext {
625
936
  * request column) is merged in by the decision pipeline, not stored here twice.
626
937
  */
627
938
  readonly metadata?: Readonly<Record<string, unknown>>;
939
+ /**
940
+ * The governed door's configured upstream name (issue #295). A string only on
941
+ * the MCP path when a name is configured; undefined on the sideband and in
942
+ * singular mode, so upstream-scoped rules are inert there by construction.
943
+ */
944
+ readonly upstream?: string;
628
945
  }
629
946
 
630
947
  /** One compiled contributor: which calls feed the budget, and from which field. */
@@ -634,6 +951,13 @@ interface CompiledBudgetContributor {
634
951
  /** Flattened conditions (one per path+operator); absent when unconditioned. */
635
952
  readonly input?: readonly InputCondition[];
636
953
  };
954
+ /**
955
+ * Configured upstream names the contributor is scoped to (issue #293);
956
+ * absent means every door contributes. A scoped contributor never
957
+ * participates when the charge context carries no upstream (sideband,
958
+ * singular mode).
959
+ */
960
+ readonly upstreams?: readonly string[];
637
961
  /** Dot-path into the tool arguments (e.g. "$.amount"), resolved per call. */
638
962
  readonly field: string;
639
963
  }
@@ -679,15 +1003,63 @@ interface JsonRpcRequest {
679
1003
  method: string;
680
1004
  params?: unknown;
681
1005
  }
1006
+ /** Resolved governance session identity (issue #218). */
1007
+ interface ResolvedSession {
1008
+ readonly id: string;
1009
+ /** Strategy that produced it — recorded in the audit trail. */
1010
+ readonly source: 'header' | 'meta' | 'legacy_header' | 'transport';
1011
+ }
682
1012
  /** A parsed MCP request enriched with session context. */
683
1013
  interface McpRequest extends JsonRpcRequest {
684
- /** MCP session ID extracted from the `Mcp-Session-Id` header. */
685
- sessionId?: string;
1014
+ /** Proxy-resolved governance session identity (undefined when no strategy matched). */
1015
+ session?: ResolvedSession;
1016
+ /** Verbatim Mcp-Session-Id from the wire. Transport relay ONLY — never governance. */
1017
+ transportSessionId?: string;
1018
+ /**
1019
+ * The client's verbatim MCP-Protocol-Version wire claim (issue #219) —
1020
+ * captured raw, with no normalization, for the audit trail. It is the
1021
+ * CLIENT'S claim, not the upstream era. Since issue #226 the
1022
+ * streamable-http route validates header/body agreement before
1023
+ * forwarding, so on a FORWARDED request a claim of `2026-07-28` has
1024
+ * passed the agreement door; tier-2 claims (legacy, unknown, malformed)
1025
+ * and notification claims remain unvalidated as captured. Streamable
1026
+ * HTTP only: the header postdates the deprecated SSE transport, and
1027
+ * stdio has no headers, so both leave it unset.
1028
+ */
1029
+ protocolVersion?: string;
686
1030
  /** Per-request headers to forward to upstream (e.g. Authorization, X-* headers). */
687
1031
  headers?: Record<string, string>;
688
1032
  /** Abort signal tied to the downstream client request lifecycle. */
689
1033
  signal?: AbortSignal;
690
1034
  }
1035
+ /**
1036
+ * An inbound request rejected by the header/body agreement door (issue
1037
+ * #226), as protocol facts: what the body said, what the headers claimed,
1038
+ * and why they disagree. Deliberately camelCase and transport-free (no Hono
1039
+ * types, no audit field names) — the audit-record mapping lives solely in
1040
+ * `buildHeaderMismatchAuditRecord` on the audit side.
1041
+ */
1042
+ interface HeaderMismatchRejection {
1043
+ /** Human-readable mismatch reason; echoed values are display-capped. */
1044
+ readonly reason: string;
1045
+ /** The body's JSON-RPC method. */
1046
+ readonly method: string;
1047
+ /** The body's params, verbatim as parsed. */
1048
+ readonly params?: unknown;
1049
+ /**
1050
+ * The body's name-bearing string field (`params.name` / `params.uri`),
1051
+ * when the method defines one and the body carries a string value.
1052
+ */
1053
+ readonly bodyName?: string;
1054
+ /** The client's verbatim MCP-Protocol-Version wire claim, if any. */
1055
+ readonly protocolVersion?: string;
1056
+ /** The inbound marker headers that were present, verbatim as received. */
1057
+ readonly headers: Readonly<Record<string, string>>;
1058
+ /** Proxy-resolved governance session identity, when a strategy matched. */
1059
+ readonly session?: ResolvedSession;
1060
+ /** Time from route-handler entry to rejection, in milliseconds. */
1061
+ readonly durationMs: number;
1062
+ }
691
1063
  /** The response returned by an MCP forwarder. */
692
1064
  interface McpResponse {
693
1065
  status: number;
@@ -724,6 +1096,13 @@ interface ServerHandle {
724
1096
  interface CreateAppOptions {
725
1097
  /** Slack interactive action handler (mounted at /slack/actions). */
726
1098
  slackActionApp?: Hono;
1099
+ /**
1100
+ * Recorder for inbound header/body agreement rejections (issue #226) on
1101
+ * the streamable-http route. Enforcement does not depend on it — with no
1102
+ * recorder the request is still rejected and no record is written (the
1103
+ * library-embedding posture `missing_tool_name` also takes).
1104
+ */
1105
+ onHeaderMismatch?: (rejection: HeaderMismatchRejection) => void;
727
1106
  }
728
1107
  /**
729
1108
  * Create a Hono app configured with the MCP proxy routes.
@@ -733,6 +1112,47 @@ interface CreateAppOptions {
733
1112
  * @param options - Optional sub-apps to mount.
734
1113
  */
735
1114
  declare function createApp(config: HelioConfig, forwarder: McpForwarder, options?: CreateAppOptions): Hono;
1115
+ /**
1116
+ * Optional sub-apps and callbacks for the multi-upstream composition.
1117
+ * The main-port posture is `createApp`'s (see CreateAppOptions): only the
1118
+ * Slack webhook callback belongs beside the MCP doors.
1119
+ */
1120
+ interface CreateMultiAppOptions {
1121
+ /** Slack interactive action handler (mounted at /slack/actions, global). */
1122
+ slackActionApp?: Hono;
1123
+ /**
1124
+ * Recorder for inbound header/body agreement rejections (issue #226),
1125
+ * called with the name of the door that rejected so the audit record can
1126
+ * attribute it. Enforcement does not depend on it.
1127
+ */
1128
+ onHeaderMismatch?: (rejection: HeaderMismatchRejection, upstreamName: string) => void;
1129
+ /**
1130
+ * @internal Test seam for the per-door concurrent-session cap on the
1131
+ * `/sse/<name>` mounts (issue #232's cap, per-route by construction), so
1132
+ * tests need not mint 1024 real streams per door. The same value goes to
1133
+ * every door's route; the session maps stay per-route-instance. Not wired
1134
+ * to config — absent means the hardcoded 1024.
1135
+ */
1136
+ sse?: {
1137
+ readonly maxConcurrentSessions?: number;
1138
+ };
1139
+ }
1140
+ /**
1141
+ * Create a Hono app serving every named upstream at its own pair of mounts,
1142
+ * `/mcp/<name>` and `/sse/<name>`, each a fresh route stack wrapping that
1143
+ * entry's forwarder. `/healthz` and `/slack/actions` stay global. There is
1144
+ * NO bare-path default: bare `/mcp`, `/sse`, and unknown names get an
1145
+ * explicit 404 catch-all whose JSON-RPC envelope states the path shape and
1146
+ * never enumerates configured names.
1147
+ *
1148
+ * @param config - The validated named-mode Helio configuration.
1149
+ * @param forwarders - One forwarder per configured entry, keyed by name.
1150
+ * Must cover the configured names exactly; missing or extra keys throw
1151
+ * (a missing key would silently mount a dead door, an extra one means the
1152
+ * record drifted from the config — fail at composition, not per request).
1153
+ * @param options - Optional sub-apps and callbacks.
1154
+ */
1155
+ declare function createMultiApp(config: HelioConfig, forwarders: Record<string, McpForwarder>, options?: CreateMultiAppOptions): Hono;
736
1156
  /**
737
1157
  * Start the HTTP server on the configured host and port.
738
1158
  *
@@ -748,6 +1168,12 @@ declare function startServer(app: Hono, config: HelioConfig): ServerHandle;
748
1168
  */
749
1169
  declare function startSidebandServer(app: Hono, port: number, host?: string): ServerHandle;
750
1170
 
1171
+ /**
1172
+ * `upstream.protocol_version`: `auto` probes and caches; a dated pin is a
1173
+ * constant — never probed, never cached, never cleared.
1174
+ */
1175
+ type UpstreamProtocolVersionPin = 'auto' | '2025-06-18' | '2026-07-28';
1176
+
751
1177
  interface StreamableHttpForwarderOptions {
752
1178
  /** The upstream MCP server URL (e.g. "http://localhost:8080/mcp"). */
753
1179
  url: string;
@@ -755,6 +1181,13 @@ interface StreamableHttpForwarderOptions {
755
1181
  headers?: Record<string, string>;
756
1182
  /** Maximum time to wait for an upstream request before aborting. */
757
1183
  requestTimeoutMs?: number;
1184
+ /** `upstream.protocol_version`: `auto` (default) probes; a dated pin skips it. */
1185
+ protocolVersion?: UpstreamProtocolVersionPin;
1186
+ /**
1187
+ * The operator-chosen upstream entry name (issue #295), threaded to the
1188
+ * session manager's era lifecycle lines. Unset in singular mode.
1189
+ */
1190
+ upstreamName?: string;
758
1191
  }
759
1192
  /**
760
1193
  * Spec-compliant upstream MCP Streamable HTTP client.
@@ -775,11 +1208,35 @@ declare class StreamableHttpForwarder implements McpForwarder {
775
1208
  /** Lifecycle parity with sse/stdio. */
776
1209
  close(): Promise<void>;
777
1210
  forward(request: McpRequest): Promise<ForwardResult>;
1211
+ /**
1212
+ * The dual-era bridge (relay leg, modern era): a modern-only server
1213
+ * answers the retired `initialize` handshake with 404/-32601, so Helio
1214
+ * synthesizes the legacy InitializeResult locally from the upstream's own
1215
+ * probe-time DiscoverResult. No `mcp-session-id` response header — the
1216
+ * legacy spec permits sessionless servers, and the downstream stays
1217
+ * sessionless. The synthesized protocolVersion is always the current
1218
+ * legacy revision, even for a client that offered an older one.
1219
+ */
1220
+ private synthesizeInitializeResult;
1221
+ /**
1222
+ * The modern upstream removed `notifications/initialized`; answer the
1223
+ * same minimal success envelope the SSE-notification path returns.
1224
+ */
1225
+ private swallowInitializedNotification;
1226
+ /**
1227
+ * The relay-side era falsification door (issue #219): a legacy-leg relay whose answer only a
1228
+ * modern server gives clears the cached legacy era (the manager no-ops on
1229
+ * pins, uncached eras, and cached modern). The response still flows to the
1230
+ * client unchanged — no in-place retry.
1231
+ */
1232
+ private inspectLegacyRelayOutcome;
778
1233
  /**
779
1234
  * Helio-internal execution path (startup prime / internal maintenance) that
780
1235
  * may borrow the proxy-managed internal session.
781
1236
  */
782
1237
  forwardInternal(request: McpRequest): Promise<ForwardResult>;
1238
+ /** Drop the managed internal session AND the cached era; next internal call re-probes. */
1239
+ resetInternalSession(): void;
783
1240
  private send;
784
1241
  }
785
1242
 
@@ -849,6 +1306,11 @@ interface StdioForwarderOptions {
849
1306
  retryDelayMs?: number;
850
1307
  /** Timeout in milliseconds for individual requests. */
851
1308
  requestTimeoutMs?: number;
1309
+ /**
1310
+ * The operator-chosen upstream entry name (multi-upstream mode). Unset in
1311
+ * singular mode — the death line then keeps its bare `[helio]` tag.
1312
+ */
1313
+ upstreamName?: string;
852
1314
  }
853
1315
  /**
854
1316
  * Forward MCP requests to an upstream server via stdio.
@@ -862,6 +1324,7 @@ declare class StdioForwarder implements McpForwarder {
862
1324
  private readonly maxRetries;
863
1325
  private readonly retryDelayMs;
864
1326
  private readonly pending;
1327
+ private readonly logTag;
865
1328
  private child;
866
1329
  private buffer;
867
1330
  private retryCount;
@@ -939,8 +1402,14 @@ interface AuditRecord {
939
1402
  readonly id: string;
940
1403
  /** ISO 8601 timestamp of when the tool call was received by the proxy. */
941
1404
  readonly timestamp: string;
942
- /** MCP session ID from the Mcp-Session-Id header, if present. */
1405
+ /** Resolved governance session id, or the adapter-supplied id on sideband records. */
943
1406
  readonly session_id: string | null;
1407
+ /**
1408
+ * Which identity strategy produced session_id (issue #218): `header`,
1409
+ * `meta`, `legacy_header`, or `transport` on the MCP door, `sideband` for
1410
+ * adapter-supplied ids. Null when no session resolved.
1411
+ */
1412
+ readonly session_source: string | null;
944
1413
  /** Agent identifier from config or request header, if present. */
945
1414
  readonly agent_id: string | null;
946
1415
  /** Runtime environment label configured on proxy startup, if set. */
@@ -1007,6 +1476,22 @@ interface AuditRecord {
1007
1476
  * `match.metadata.*` (#13) and the dashboard metadata columns (#16).
1008
1477
  */
1009
1478
  readonly metadata: Record<string, unknown> | null;
1479
+ /**
1480
+ * The client's verbatim MCP-Protocol-Version wire claim on the inbound
1481
+ * request (issue #219) — the CLIENT'S claim, not the upstream era, and not
1482
+ * a validated fact. Recorded uncapped: truncation would manufacture a
1483
+ * false claim in an audit record. Null when the request carried no header
1484
+ * and on records with no MCP wire (drift events, sideband records).
1485
+ */
1486
+ readonly protocol_version: string | null;
1487
+ /**
1488
+ * Name of the upstream MCP server this record is attributed to (issue
1489
+ * #292). Null in singular mode (no named upstreams configured), on
1490
+ * sideband records (no MCP door), and on rows that predate the column.
1491
+ * Required-nullable like session_source/protocol_version: a producer that
1492
+ * could silently omit it would exempt that door from attribution.
1493
+ */
1494
+ readonly upstream: string | null;
1010
1495
  /** ISO 8601 timestamp of when the record was persisted. */
1011
1496
  readonly created_at: string;
1012
1497
  }
@@ -1022,6 +1507,18 @@ interface AuditQueryFilters {
1022
1507
  readonly blocked?: boolean;
1023
1508
  /** Filter by session ID (exact match). */
1024
1509
  readonly session_id?: string;
1510
+ /**
1511
+ * Filter by session identity source (exact match — the five-value
1512
+ * `header` / `meta` / `legacy_header` / `transport` / `sideband`
1513
+ * vocabulary, issue #250). SQL equality: rows whose session_source is
1514
+ * NULL never match any filter value.
1515
+ */
1516
+ readonly session_source?: string;
1517
+ /**
1518
+ * Filter by upstream name (exact match). SQL equality: rows whose
1519
+ * upstream is NULL never match any filter value.
1520
+ */
1521
+ readonly upstream?: string;
1025
1522
  /** Filter by agent ID (exact match). */
1026
1523
  readonly agent_id?: string;
1027
1524
  /** Include only records created at or after this ISO 8601 timestamp. */
@@ -1090,9 +1587,15 @@ interface AuditAggregateStats {
1090
1587
  readonly reason: string;
1091
1588
  readonly count: number;
1092
1589
  }>;
1093
- /** Top tools by call count (max 10). */
1590
+ /**
1591
+ * Top tools by call count (max 10 rows). Each row is a (tool, upstream)
1592
+ * pair (issue #297): same-named tools on different upstreams stay
1593
+ * distinct. Upstream is null in singular mode, so all-null groups reduce
1594
+ * to exactly the pre-#297 per-tool rows.
1595
+ */
1094
1596
  readonly top_tools: ReadonlyArray<{
1095
1597
  readonly tool_name: string;
1598
+ readonly upstream: string | null;
1096
1599
  readonly count: number;
1097
1600
  }>;
1098
1601
  /** Approval rate (approved / total require_approval decisions), or null if none. */
@@ -1215,8 +1718,15 @@ declare class AuditStore {
1215
1718
  private query;
1216
1719
  /** Count records matching the given filters. */
1217
1720
  count(filters?: AuditQueryFilters): number;
1218
- /** Get aggregate statistics for a time range. */
1219
- aggregate(from?: string, to?: string): AuditAggregateStats;
1721
+ /**
1722
+ * Get aggregate statistics for a time range. The optional upstream filter
1723
+ * scopes EVERY sub-aggregate (totals, by_decision, by_block_reason,
1724
+ * top_tools, approval_rate, per_hour) — "analytics for this door", not one
1725
+ * filtered chart. Exact match: null-upstream rows never match any value.
1726
+ */
1727
+ aggregate(from?: string, to?: string, filters?: {
1728
+ upstream?: string;
1729
+ }): AuditAggregateStats;
1220
1730
  /** Delete records older than the retention period. Returns the count of deleted records. */
1221
1731
  purgeExpired(): number;
1222
1732
  private purgeBefore;
@@ -1504,6 +2014,18 @@ interface ApprovalTicket {
1504
2014
  readonly rule_index: number | null;
1505
2015
  readonly channel_name: string;
1506
2016
  readonly session_id: string | null;
2017
+ /**
2018
+ * Which identity strategy produced session_id (issue #251): the MCP
2019
+ * resolver's vocabulary on proxy tickets, `sideband` on adapter tickets.
2020
+ * OPTIONAL-ABSENT, not nullable: the ticket is the wire object, and an
2021
+ * unattributed ticket omits the key rather than emitting null.
2022
+ */
2023
+ readonly session_source?: string;
2024
+ /**
2025
+ * Upstream attribution (issue #292). Absent in singular mode and on
2026
+ * native (sideband) tickets — same wire-additive optional spelling.
2027
+ */
2028
+ readonly upstream?: string;
1507
2029
  readonly requested_at: string;
1508
2030
  readonly timeout_at: string;
1509
2031
  readonly timeout_ms: number;
@@ -1599,6 +2121,10 @@ declare class ApprovalQueue {
1599
2121
  timeout_ms: number;
1600
2122
  /** Breached budget context on break-glass / merged tickets (issue #14). */
1601
2123
  breached_budgets?: readonly BudgetBreachContext[];
2124
+ /** Identity-strategy attribution for session_id (issue #251). */
2125
+ session_source?: string | null;
2126
+ /** Upstream attribution (issue #292); absent in singular mode. */
2127
+ upstream?: string | null;
1602
2128
  }): ApprovalTicket;
1603
2129
  /** Get a ticket by ID. Returns undefined if not found. */
1604
2130
  get(id: string): ApprovalTicket | undefined;
@@ -1654,6 +2180,13 @@ interface ApprovalSubmitParams {
1654
2180
  readonly tool_input: Record<string, unknown>;
1655
2181
  readonly matched_rule: CompiledPolicyRule | undefined;
1656
2182
  readonly session_id: string | null;
2183
+ /**
2184
+ * Identity-strategy attribution for session_id (issue #251).
2185
+ * Required-nullable so no submit site can silently forget it.
2186
+ */
2187
+ readonly session_source: string | null;
2188
+ /** Upstream attribution (issue #292); null in singular mode. */
2189
+ readonly upstream: string | null;
1657
2190
  /** Breached budget context; marks the ticket as break-glass (issue #14). */
1658
2191
  readonly breached_budgets?: readonly BudgetBreachContext[];
1659
2192
  /**
@@ -1861,12 +2394,21 @@ declare class RateLimiter {
1861
2394
  * Reconcile bucket state against a new policy's limit configuration.
1862
2395
  *
1863
2396
  * Walks every existing bucket and checks whether its last-seen
1864
- * `{ maxCalls, windowMs }` tuple still appears anywhere in `validConfigs`.
2397
+ * `{ maxCalls, windowMs }` tuple still appears in `validConfigs`.
1865
2398
  * Buckets whose config is still present are left untouched — counters and
1866
2399
  * elapsed-window progress are preserved across hot-reloads. Buckets whose
1867
2400
  * config is gone (rule changed or removed) are evicted so the next check
1868
2401
  * lazy-creates a fresh bucket under the new config.
1869
2402
  *
2403
+ * Keys built by `ruleBucketKey` (bucket-key.ts) carry the owning rule's
2404
+ * index, and for those the tuple must match at THAT index
2405
+ * (`config.ruleIndex`): a reorder that shifts a rate rule's index evicts
2406
+ * its old-index bucket instead of leaving an orphan no rule reads again —
2407
+ * or worse, letting whatever rule now sits at that index adopt another
2408
+ * rule's accrued calls. Un-suffixed keys keep the tuple-anywhere match,
2409
+ * but only against index-less configs — a caller that passes only indexed
2410
+ * configs (as the proxy does) evicts every un-suffixed bucket, fail-closed.
2411
+ *
1870
2412
  * This is the compare-and-evict semantic that replaces the old `reset()`
1871
2413
  * call on every hot-reload, which wiped all state even when the matching
1872
2414
  * rule was unchanged.
@@ -1874,6 +2416,7 @@ declare class RateLimiter {
1874
2416
  reconcile(validConfigs: Iterable<{
1875
2417
  maxCalls: number;
1876
2418
  windowMs: number;
2419
+ ruleIndex?: number;
1877
2420
  }>): void;
1878
2421
  /** Stop the cleanup timer and mark as closed. */
1879
2422
  /**
@@ -2002,12 +2545,14 @@ declare class SpendLimiter {
2002
2545
  * whose config is gone (rule changed or removed) are evicted so the next
2003
2546
  * check lazy-creates a fresh bucket under the new config.
2004
2547
  *
2005
- * Keys built by {@link spendBucketKey} carry the owning rule's index, and
2006
- * for those the tuple must match at THAT index (`config.ruleIndex`): a
2548
+ * Keys built by `ruleBucketKey` (bucket-key.ts) carry the owning rule's
2549
+ * index, and for those the tuple must match at THAT index (`config.ruleIndex`): a
2007
2550
  * reorder that shifts a spend rule's index evicts its old-index bucket
2008
2551
  * instead of leaving an orphan no rule reads again — or worse, letting
2009
2552
  * whatever rule now sits at that index adopt another rule's accrued spend.
2010
- * Un-suffixed keys keep the tuple-anywhere match.
2553
+ * Un-suffixed keys keep the tuple-anywhere match, but only against
2554
+ * index-less configs — a caller that passes only indexed configs (as the
2555
+ * proxy does) evicts every un-suffixed bucket, fail-closed.
2011
2556
  *
2012
2557
  * Currency is part of the tuple because a USD→EUR switch is a meaningful
2013
2558
  * policy change — the same numeric limit buys a different amount of real
@@ -2032,13 +2577,37 @@ declare class SpendLimiter {
2032
2577
  close(): void;
2033
2578
  }
2034
2579
 
2580
+ declare const GATED_SESSION: unique symbol;
2581
+ /** A session bucket value that provably passed the identity gate. */
2582
+ type GatedSession = string & {
2583
+ readonly [GATED_SESSION]: true;
2584
+ };
2585
+ declare const GATED_CHARGES: unique symbol;
2586
+ /** Budget charges that provably passed the session engagement check. */
2587
+ type GatedCharges = readonly BudgetCharge[] & {
2588
+ readonly [GATED_CHARGES]: true;
2589
+ };
2590
+
2035
2591
  /** Everything the engine needs about one tool call to resolve its charges. */
2036
2592
  interface BudgetChargeContext {
2037
2593
  readonly toolName: string;
2038
2594
  readonly toolArguments: Record<string, unknown> | undefined;
2039
- readonly sessionId: string | null;
2595
+ /**
2596
+ * Gate-minted session bucket value (issue #218), or null when identity is
2597
+ * unresolved — `bucketKey` builds `session:unknown` from null, which is
2598
+ * legal ONLY because `gateBudgetCharges` denies unresolved session-keyed
2599
+ * engagement before `peekAll`/`recordAll` can run.
2600
+ */
2601
+ readonly sessionId: GatedSession | null;
2040
2602
  /** Adapter-supplied sender id (sideband only); null on the MCP path. */
2041
2603
  readonly senderId: string | null;
2604
+ /**
2605
+ * The governed door's configured upstream name (issue #295); null on the
2606
+ * sideband and in singular mode. Required so every door states its
2607
+ * upstream: a silently-missing value would exempt that door's calls from
2608
+ * upstream-scoped contributors once those exist.
2609
+ */
2610
+ readonly upstream: string | null;
2042
2611
  }
2043
2612
  /** One budget's share of a call: which bucket, how much. */
2044
2613
  interface BudgetCharge {
@@ -2052,6 +2621,12 @@ interface BudgetCharge {
2052
2621
  * NOT repopulate the new pot with old-config spend — recordAll skips it.
2053
2622
  */
2054
2623
  readonly generation: number;
2624
+ /**
2625
+ * The charge context's upstream name, stamped only when the door set one
2626
+ * (issue #295). Optional is the honest type: `remintDeferredCharges`
2627
+ * rebuilds charges without it, so deferred commits snapshot null.
2628
+ */
2629
+ readonly upstream?: string;
2055
2630
  }
2056
2631
  /** A budget whose contributor matched but whose amount was unusable. */
2057
2632
  interface BudgetChargeFailure {
@@ -2076,6 +2651,11 @@ interface BudgetPeekEntry {
2076
2651
  readonly remaining: number;
2077
2652
  /** Epoch ms when the oldest entry ages out (duration); null for session pots. */
2078
2653
  readonly resetAtMs: number | null;
2654
+ /**
2655
+ * The charge's upstream label, or null: sideband calls, singular mode, and
2656
+ * reminted deferred commits all snapshot null (issue #295).
2657
+ */
2658
+ readonly upstream: string | null;
2079
2659
  /**
2080
2660
  * Set on recordAll snapshots for charges frozen before a tuple-changing
2081
2661
  * reload: the executed spend was ledgered under its evaluate-time
@@ -2140,6 +2720,12 @@ interface BudgetCommitEvent {
2140
2720
  readonly limit: number;
2141
2721
  readonly currency: string;
2142
2722
  readonly utilization: number;
2723
+ /**
2724
+ * Upstream attribution from the charge context (issue #292), or null.
2725
+ * Reminted frozen-plan charges drop the label by design, so deferred
2726
+ * sideband commits always emit null here.
2727
+ */
2728
+ readonly upstream: string | null;
2143
2729
  }
2144
2730
  /**
2145
2731
  * Payload for the per-budget breach callback (dashboard event bus). Fired
@@ -2156,6 +2742,8 @@ interface BudgetBreachEvent {
2156
2742
  readonly spent: number;
2157
2743
  readonly limit: number;
2158
2744
  readonly currency: string;
2745
+ /** Upstream attribution from the peek entry's context (issue #292), or null. */
2746
+ readonly upstream: string | null;
2159
2747
  }
2160
2748
  /** Wire-ready bucket state for `GET /api/budgets` (snake_case DTO). */
2161
2749
  interface BudgetBucketState {
@@ -2207,21 +2795,29 @@ declare class BudgetEngine {
2207
2795
  /**
2208
2796
  * Resolve which budgets a call feeds and how much it charges each.
2209
2797
  *
2210
- * A contributor participates when its tool glob matches the tool name AND
2211
- * every `match.input` condition holds (absent conditions means the glob
2212
- * alone decides); the FIRST participating contributor (config order, over
2213
- * that combined predicate) supplies the amount field. A call that matches
2214
- * the glob but not the conditions simply does not feed the budget — no
2215
- * charge, no failure. Once a contributor is selected, a missing,
2216
- * non-numeric, negative, or non-finite amount fails closed as a `failures`
2217
- * entry the caller must deny the call.
2798
+ * A contributor participates when its upstream scope admits the call's
2799
+ * door (absent scope admits every door; a scoped contributor never
2800
+ * participates when `ctx.upstream` is null sideband, singular mode) AND
2801
+ * its tool glob matches the tool name AND every `match.input` condition
2802
+ * holds (absent conditions means the glob alone decides); the FIRST
2803
+ * participating contributor (config order, over that combined predicate)
2804
+ * supplies the amount field. A call that matches the glob but not the
2805
+ * conditions or the scope simply does not feed the budget — no charge, no
2806
+ * failure — and a later contributor may still participate. Once a
2807
+ * contributor is selected, a missing, non-numeric, negative, or non-finite
2808
+ * amount fails closed as a `failures` entry — the caller must deny the
2809
+ * call.
2218
2810
  */
2219
2811
  resolveCharges(ctx: BudgetChargeContext): {
2220
2812
  charges: BudgetCharge[];
2221
2813
  failures: BudgetChargeFailure[];
2222
2814
  };
2223
- /** Check every charge without mutating. All-or-nothing: one deny flips `allowed`. */
2224
- peekAll(charges: readonly BudgetCharge[]): {
2815
+ /**
2816
+ * Check every charge without mutating. All-or-nothing: one deny flips
2817
+ * `allowed`. Accepts only gate-branded charges (issue #218) — a caller
2818
+ * cannot peek budget state without having run the session engagement check.
2819
+ */
2820
+ peekAll(charges: GatedCharges): {
2225
2821
  allowed: boolean;
2226
2822
  entries: BudgetPeekEntry[];
2227
2823
  };
@@ -2232,7 +2828,7 @@ declare class BudgetEngine {
2232
2828
  * Recording is unconditional past the sink (an approved overage
2233
2829
  * legitimately pushes a bucket past its limit).
2234
2830
  */
2235
- recordAll(charges: readonly BudgetCharge[], meta: BudgetCommitMeta): BudgetPeekEntry[];
2831
+ recordAll(charges: GatedCharges, meta: BudgetCommitMeta): BudgetPeekEntry[];
2236
2832
  /**
2237
2833
  * Fire one `onBreach` event per breached entry. Called by the doors at the
2238
2834
  * moment a peek outcome actually denies the call or raises the composite
@@ -2329,6 +2925,18 @@ declare class BudgetEngine {
2329
2925
  private snapshot;
2330
2926
  }
2331
2927
 
2928
+ /** Compiled session config, wired into the route factories at startup. */
2929
+ interface CompiledSessionIdentity {
2930
+ /** The configured identity chain, in order; first match wins. */
2931
+ readonly sources: readonly SessionIdentitySource[];
2932
+ readonly onUnresolved: 'deny' | 'anonymous';
2933
+ /**
2934
+ * Human-readable strategy list (e.g. `header "x-helio-session-id",
2935
+ * legacy_header`) — deny messages name the strategies that were tried.
2936
+ */
2937
+ readonly strategySummary: string;
2938
+ }
2939
+
2332
2940
  /** Options for constructing a GovernedForwarder. */
2333
2941
  interface GovernedForwarderOptions {
2334
2942
  /** The current environment label (e.g. "production", "staging"). */
@@ -2345,6 +2953,19 @@ interface GovernedForwarderOptions {
2345
2953
  spendLimiter?: SpendLimiter;
2346
2954
  /** Budget engine for named cross-tool budgets (issue #14). */
2347
2955
  budgetEngine?: BudgetEngine;
2956
+ /**
2957
+ * Compiled session identity config (issue #218): the `on_unresolved` mode
2958
+ * and the strategy summary deny messages name. Defaults to the schema
2959
+ * default chain (deny mode) for direct/library construction.
2960
+ */
2961
+ session?: CompiledSessionIdentity;
2962
+ /**
2963
+ * The operator-chosen upstream entry name (issue #295, multi-upstream
2964
+ * substrate). Unset in singular mode — every governance surface then
2965
+ * behaves exactly as today. The multi-upstream composition loop (issue
2966
+ * #294) passes each entry's name.
2967
+ */
2968
+ upstreamName?: string;
2348
2969
  }
2349
2970
  /** Result of attempting to prime the tool annotation cache. */
2350
2971
  interface AnnotationCachePrimeResult {
@@ -2367,12 +2988,14 @@ declare class GovernedForwarder implements McpForwarder {
2367
2988
  private readonly inner;
2368
2989
  private policy;
2369
2990
  private readonly environment;
2991
+ private readonly session;
2370
2992
  private readonly auditWriter;
2371
2993
  private readonly evidenceStore;
2372
2994
  private readonly approvalRouter;
2373
2995
  private readonly rateLimiter;
2374
2996
  private readonly spendLimiter;
2375
2997
  private readonly budgetEngine;
2998
+ private readonly upstreamName;
2376
2999
  private readonly annotationCache;
2377
3000
  private agentKeyWarned;
2378
3001
  private senderKeyWarned;
@@ -2406,6 +3029,20 @@ declare class GovernedForwarder implements McpForwarder {
2406
3029
  */
2407
3030
  primeAnnotationCache(): Promise<AnnotationCachePrimeResult>;
2408
3031
  forward(request: McpRequest): Promise<ForwardResult>;
3032
+ /**
3033
+ * Clamp an over-long `result.ttlMs` on a `tools/list` response to
3034
+ * `policies.tool_revalidation.max_advertised_ttl` (issue #221, D7).
3035
+ *
3036
+ * Downward-only: a `ttlMs` at or below the cap is left untouched, and a
3037
+ * response with no `ttlMs` never gains one — Helio does not manufacture a
3038
+ * cache hint the upstream never advertised. Non-numeric values are left
3039
+ * alone rather than coerced. `cacheScope` passes through untouched: Helio
3040
+ * baselines and vouches for tool *definitions* only, and its own
3041
+ * `tools/list` view is not caller-varying, so it has no basis to alter a
3042
+ * scope hint the upstream set. No-op when tool revalidation is disabled
3043
+ * (including hand-built `CompiledPolicy` fixtures that omit the field).
3044
+ */
3045
+ private clampCacheHints;
2409
3046
  /**
2410
3047
  * Apply a tools/list response to the definition cache and surface any
2411
3048
  * drift: console warning + immediate audit record per event. Single entry
@@ -2469,14 +3106,22 @@ declare class GovernedForwarder implements McpForwarder {
2469
3106
  * or consuming any resources. Returns an MCP-compliant synthetic response.
2470
3107
  */
2471
3108
  private handleDryRun;
2472
- /** Construct a limit bucket key based on the configured key type. */
3109
+ /**
3110
+ * Construct a non-session limit bucket key. Tool-scope keys route through
3111
+ * the shared `toolLimitKey` leaf, which prefixes them with the configured
3112
+ * upstream name when one is set (issue #295). Session keys are deliberately
3113
+ * NOT built here: they come only from the gate module's `sessionLimitKey`,
3114
+ * whose `GatedSession` parameter makes skipping the identity gate a
3115
+ * compile error (issue #218) — call sites branch on `key === 'session'`.
3116
+ */
2473
3117
  private buildLimitKey;
2474
3118
  /**
2475
- * Construct a spend bucket key via the shared {@link spendBucketKey}
2476
- * composer see its doc for why spend buckets are rule-discriminated.
2477
- * Rate buckets keep the undiscriminated keys.
3119
+ * Gate a session-keyed limit at its key-build site (issue #218). Returns
3120
+ * the bucket key, or null when identity is unresolved under deny mode —
3121
+ * the caller denies (enforce) or reports the marker (dry-run).
2478
3122
  */
2479
- private buildSpendLimitKey;
3123
+ private gateSessionLimitKey;
3124
+ private makeSessionUnresolvedResult;
2480
3125
  private writeAuditRecord;
2481
3126
  private makeDriftBlockResult;
2482
3127
  private makeDenyResult;
@@ -2521,6 +3166,13 @@ interface BudgetEventRecord {
2521
3166
  readonly timestamp: string;
2522
3167
  readonly timestamp_ms: number;
2523
3168
  readonly created_at: string;
3169
+ /**
3170
+ * Upstream attribution read from the referenced audit record via LEFT
3171
+ * JOIN (issue #292) — never a stored ledger column. Null on singular-mode
3172
+ * records, dangling or null `audit_record_id`, and rows predating the
3173
+ * audit column; a null renders as empty/absent, never a made-up label.
3174
+ */
3175
+ readonly upstream: string | null;
2524
3176
  }
2525
3177
  /** One page of a budget's event history plus the unpaginated total. */
2526
3178
  interface BudgetEventsPage {
@@ -2611,6 +3263,13 @@ interface GovernanceServiceOptions {
2611
3263
  readonly spendLimiter?: SpendLimiter;
2612
3264
  /** Budget engine for named cross-tool budgets (issue #14). */
2613
3265
  readonly budgetEngine?: BudgetEngine;
3266
+ /**
3267
+ * Compiled session identity config (issue #218): the sideband door gets
3268
+ * the SAME on_unresolved policy as the MCP door — an adapter omitting
3269
+ * session_id while a session-keyed control is engaged is denied under
3270
+ * `deny`, pools under `anonymous`. Defaults to the schema default chain.
3271
+ */
3272
+ readonly session?: CompiledSessionIdentity;
2614
3273
  readonly auditWriter?: AuditWriter;
2615
3274
  /** Default approval timeout (ms) when a rule sets none. */
2616
3275
  readonly approvalTimeoutMs?: number;
@@ -2630,6 +3289,7 @@ interface GovernanceServiceOptions {
2630
3289
  declare class GovernanceService {
2631
3290
  private policy;
2632
3291
  private readonly environment;
3292
+ private readonly session;
2633
3293
  private readonly evidenceStore;
2634
3294
  private readonly approvalRouter;
2635
3295
  private readonly rateLimiter;
@@ -2789,6 +3449,22 @@ declare class GovernanceConfigError extends Error {
2789
3449
  constructor(message: string);
2790
3450
  }
2791
3451
 
3452
+ /**
3453
+ * Map a header/body agreement rejection (issue #226) onto the audit record
3454
+ * shape, mirroring the `missing_tool_name` precedent: `policy_decision:
3455
+ * 'rejected'`, no rule evaluated, nothing forwarded, written by the caller
3456
+ * via `pushImmediate`. The snake_case mapping lives here alone — the
3457
+ * rejection payload itself stays camelCase protocol facts (`mcp/types.ts`).
3458
+ *
3459
+ * `record_kind` stays `'tool_call'` for the whole class, non-tool methods
3460
+ * included: it is the MCP-door request bucket, and `block_reason` is the
3461
+ * query key. `tool_name` carries the body's name-bearing field (the body
3462
+ * truth an investigator filters for) when the method has one, else the
3463
+ * reserved `'<header_mismatch>'` sentinel — either way `top_tools` already
3464
+ * excludes rejected records, so these rows never pollute tool rankings.
3465
+ */
3466
+ declare function buildHeaderMismatchAuditRecord(rejection: HeaderMismatchRejection, environment?: string, upstream?: string): Omit<AuditRecord, 'id' | 'created_at'>;
3467
+
2792
3468
  /** @internal Exported for testing only. */
2793
3469
  declare class QueueChannel implements ApprovalChannel {
2794
3470
  readonly type = "dashboard";
@@ -2915,6 +3591,10 @@ interface ActionEvent {
2915
3591
  readonly block_reason: string | null;
2916
3592
  readonly approval_status: string | null;
2917
3593
  readonly session_id: string | null;
3594
+ /** Identity strategy that produced session_id (issue #218), or null. */
3595
+ readonly session_source: string | null;
3596
+ /** The client's verbatim MCP-Protocol-Version wire claim (issue #219), or null. */
3597
+ readonly protocol_version: string | null;
2918
3598
  readonly agent_id: string | null;
2919
3599
  readonly environment: string | null;
2920
3600
  readonly timestamp: string;
@@ -2927,6 +3607,8 @@ interface ActionEvent {
2927
3607
  readonly matched_rule_index: number | null;
2928
3608
  readonly record_kind: AuditRecord['record_kind'];
2929
3609
  readonly origin: string;
3610
+ /** Upstream attribution from the audit record (issue #292), or null. */
3611
+ readonly upstream: string | null;
2930
3612
  }
2931
3613
  /** Payload for an approval_requested event. */
2932
3614
  interface ApprovalRequestedEvent {
@@ -2934,6 +3616,8 @@ interface ApprovalRequestedEvent {
2934
3616
  readonly tool_name: string;
2935
3617
  readonly channel: string;
2936
3618
  readonly requested_at: string;
3619
+ /** Upstream attribution from the ticket (issue #292), or null. */
3620
+ readonly upstream: string | null;
2937
3621
  }
2938
3622
  /** Payload for an approval_resolved event. */
2939
3623
  interface ApprovalResolvedEvent {
@@ -2949,6 +3633,11 @@ interface LimitWarningEvent {
2949
3633
  readonly current: number;
2950
3634
  readonly limit: number;
2951
3635
  readonly utilization: number;
3636
+ /**
3637
+ * Upstream name parsed from a partitioned bucket key (issue #292), or
3638
+ * null — session keys, singular tool keys, and sideband keys have no door.
3639
+ */
3640
+ readonly upstream: string | null;
2952
3641
  }
2953
3642
  /** Payload for approval notification delivery failures. */
2954
3643
  interface ApprovalNotificationFailedEvent {
@@ -3052,6 +3741,20 @@ interface DashboardAppOptions {
3052
3741
  readonly staticDir?: string;
3053
3742
  /** SSE heartbeat interval in milliseconds. Defaults to 30 000 (30s). */
3054
3743
  readonly sseHeartbeatMs?: number;
3744
+ /**
3745
+ * @internal Test seam for the concurrent-connection cap (issue #285), so
3746
+ * tests need not mint 256 real streams. Not wired to config — the cap is
3747
+ * a hardcoded invariant (see MAX_SSE_CONNECTIONS).
3748
+ */
3749
+ readonly maxSseConnections?: number;
3750
+ /**
3751
+ * @internal Test seam for the stale-connection sweep cadence (issue
3752
+ * #327), so sweeper tests need not wait out the production floor. `0`
3753
+ * DISABLES the sweeper entirely (the repo-wide sweep-interval
3754
+ * convention). Not wired to config — the 10 s floor stays the
3755
+ * production default.
3756
+ */
3757
+ readonly sweepIntervalMs?: number;
3055
3758
  }
3056
3759
  /**
3057
3760
  * Create a Hono app for the dashboard REST + SSE API.
@@ -3061,4 +3764,4 @@ interface DashboardAppOptions {
3061
3764
  */
3062
3765
  declare function createDashboardApp(deps: DashboardAppDeps, options?: DashboardAppOptions): Hono;
3063
3766
 
3064
- export { type AdapterLivenessEntry, type ApprovalAppOptions, type ApprovalChannel, type ApprovalOutcome, ApprovalQueue, type ApprovalQueueOptions, ApprovalRouter, type ApprovalRouterOptions, type ApprovalStatus, type ApprovalTicket, type AuditAggregateStats, type AuditInput, type AuditListResult, type AuditPaginationOptions, type AuditQueryFilters, type AuditRecord, AuditStore, type AuditStoreOptions, type AuditTimeBucket, AuditWriter, type AuditWriterOptions, type BudgetBreachContext, type BudgetBreachEvent, type BudgetBucketState, type BudgetCommitEvent, BudgetEngine, type BudgetEventRecord, type BudgetEventsPage, type BudgetLedgerRow, type BudgetLedgerSink, BudgetParseError, type BudgetState, type CompilePoliciesResult, type CompiledBudget, type CompiledBudgetContributor, type CompiledBudgetWindow, type CompiledPolicy, type CompiledPolicyRule, ConfigError, type CreateAppOptions, type DashboardAppDeps, type DashboardAppOptions, DashboardEventBus, type DashboardEventType, type DashboardEvents, EXPORT_MAX_RECORDS, type EvaluateInput, type EvidenceEntry, EvidenceStore, type EvidenceStoreOptions, GovernanceConfigError, GovernanceService, type GovernanceServiceOptions, GovernedForwarder, type GovernedForwarderOptions, type HelioConfig, type InstallScanInput, LIST_MAX_PAGE_SIZE, type MatchContext, type PolicyDecision, PolicyParseError, QueueChannel, type RateLimitCheckParams, type RateLimitKeyState, type RateLimitResult, RateLimiter, type RateLimiterOptions, type ResolveApprovalInput, type ServerHandle, type SessionState, type SlackActionAppOptions, SlackChannel, type SlackChannelOptions, type SpendLimitCheckParams, type SpendLimitKeyState, type SpendLimitResult, SpendLimiter, type SpendLimiterOptions, SseUpstreamForwarder, type SseUpstreamForwarderOptions, StdioForwarder, type StdioForwarderOptions, StreamableHttpForwarder, type StreamableHttpForwarderOptions, UpstreamForwarder, type UpstreamForwarderOptions, VERSION, WebhookChannel, type WebhookChannelOptions, type WireDecision, compileBudgets, compilePolicies, createApp, createApprovalApp, createChannels, createDashboardApp, createSidebandApp, createSlackActionApp, evaluatePolicy, loadConfig, matchRule, startServer, startSidebandServer };
3767
+ export { type AdapterLivenessEntry, type ApprovalAppOptions, type ApprovalChannel, type ApprovalOutcome, ApprovalQueue, type ApprovalQueueOptions, ApprovalRouter, type ApprovalRouterOptions, type ApprovalStatus, type ApprovalTicket, type AuditAggregateStats, type AuditInput, type AuditListResult, type AuditPaginationOptions, type AuditQueryFilters, type AuditRecord, AuditStore, type AuditStoreOptions, type AuditTimeBucket, AuditWriter, type AuditWriterOptions, type BudgetBreachContext, type BudgetBreachEvent, type BudgetBucketState, type BudgetCommitEvent, BudgetEngine, type BudgetEventRecord, type BudgetEventsPage, type BudgetLedgerRow, type BudgetLedgerSink, BudgetParseError, type BudgetState, type CompilePoliciesResult, type CompiledBudget, type CompiledBudgetContributor, type CompiledBudgetWindow, type CompiledPolicy, type CompiledPolicyRule, ConfigError, type CreateAppOptions, type CreateMultiAppOptions, type DashboardAppDeps, type DashboardAppOptions, DashboardEventBus, type DashboardEventType, type DashboardEvents, EXPORT_MAX_RECORDS, type EvaluateInput, type EvidenceEntry, EvidenceStore, type EvidenceStoreOptions, GovernanceConfigError, GovernanceService, type GovernanceServiceOptions, GovernedForwarder, type GovernedForwarderOptions, type HeaderMismatchRejection, type HelioConfig, type InstallScanInput, LIST_MAX_PAGE_SIZE, type MatchContext, type NamedHelioConfig, type PolicyDecision, PolicyParseError, QueueChannel, type RateLimitCheckParams, type RateLimitKeyState, type RateLimitResult, RateLimiter, type RateLimiterOptions, type ResolveApprovalInput, type ServerHandle, type SessionState, type SingularHelioConfig, type SlackActionAppOptions, SlackChannel, type SlackChannelOptions, type SpendLimitCheckParams, type SpendLimitKeyState, type SpendLimitResult, SpendLimiter, type SpendLimiterOptions, SseUpstreamForwarder, type SseUpstreamForwarderOptions, StdioForwarder, type StdioForwarderOptions, StreamableHttpForwarder, type StreamableHttpForwarderOptions, UpstreamForwarder, type UpstreamForwarderOptions, VERSION, WebhookChannel, type WebhookChannelOptions, type WireDecision, buildHeaderMismatchAuditRecord, compileBudgets, compilePolicies, createApp, createApprovalApp, createChannels, createDashboardApp, createMultiApp, createSidebandApp, createSlackActionApp, evaluatePolicy, isNamedConfig, isSingularConfig, loadConfig, matchRule, startServer, startSidebandServer };