@gethelio/proxy 0.11.1 → 0.12.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.
@@ -6,8 +6,8 @@
6
6
  <meta name="referrer" content="no-referrer" />
7
7
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
8
  <title>Helio Dashboard</title>
9
- <script type="module" crossorigin src="/assets/index-D19fYKEH.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-CPoQ6xns.css">
9
+ <script type="module" crossorigin src="/assets/index-uJng9NyO.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-BBYXsIig.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
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";
@@ -45,8 +53,8 @@ declare const policiesSchema: z.ZodObject<{
45
53
  }, z.core.$strict>]>>>;
46
54
  }, z.core.$strict>;
47
55
  action: z.ZodEnum<{
48
- allow: "allow";
49
56
  deny: "deny";
57
+ allow: "allow";
50
58
  require_approval: "require_approval";
51
59
  rate_limit: "rate_limit";
52
60
  spend_limit: "spend_limit";
@@ -92,8 +100,8 @@ declare const policiesSchema: z.ZodObject<{
92
100
  }, z.core.$strict>>>;
93
101
  install: z.ZodOptional<z.ZodObject<{
94
102
  default: z.ZodDefault<z.ZodEnum<{
95
- allow: "allow";
96
103
  deny: "deny";
104
+ allow: "allow";
97
105
  }>>;
98
106
  rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
99
107
  name: z.ZodOptional<z.ZodString>;
@@ -122,6 +130,11 @@ declare const policiesSchema: z.ZodObject<{
122
130
  log: "log";
123
131
  block: "block";
124
132
  }>>;
133
+ tool_revalidation: z.ZodOptional<z.ZodObject<{
134
+ enabled: z.ZodDefault<z.ZodBoolean>;
135
+ interval: z.ZodDefault<z.ZodString>;
136
+ max_advertised_ttl: z.ZodOptional<z.ZodString>;
137
+ }, z.core.$strict>>;
125
138
  hot_reload: z.ZodOptional<z.ZodBoolean>;
126
139
  }, z.core.$strict>;
127
140
  declare const budgetSchema: z.ZodObject<{
@@ -187,6 +200,11 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
187
200
  sse: "sse";
188
201
  stdio: "stdio";
189
202
  }>>;
203
+ protocol_version: z.ZodDefault<z.ZodEnum<{
204
+ auto: "auto";
205
+ "2025-06-18": "2025-06-18";
206
+ "2026-07-28": "2026-07-28";
207
+ }>>;
190
208
  command: z.ZodOptional<z.ZodString>;
191
209
  args: z.ZodOptional<z.ZodArray<z.ZodString>>;
192
210
  connect_timeout: z.ZodDefault<z.ZodString>;
@@ -197,12 +215,27 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
197
215
  listen: z.ZodPrefault<z.ZodObject<{
198
216
  port: z.ZodDefault<z.ZodNumber>;
199
217
  host: z.ZodDefault<z.ZodString>;
218
+ allowed_origins: z.ZodDefault<z.ZodArray<z.ZodString>>;
200
219
  }, z.core.$strict>>;
201
220
  environment: z.ZodOptional<z.ZodString>;
221
+ session: z.ZodPrefault<z.ZodObject<{
222
+ identity: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
223
+ source: z.ZodLiteral<"header">;
224
+ name: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>;
225
+ }, z.core.$strict>, z.ZodObject<{
226
+ source: z.ZodLiteral<"meta">;
227
+ }, z.core.$strict>, z.ZodObject<{
228
+ source: z.ZodLiteral<"legacy_header">;
229
+ }, z.core.$strict>], "source">>>;
230
+ on_unresolved: z.ZodDefault<z.ZodEnum<{
231
+ deny: "deny";
232
+ anonymous: "anonymous";
233
+ }>>;
234
+ }, z.core.$strict>>;
202
235
  policies: z.ZodPrefault<z.ZodObject<{
203
236
  default: z.ZodDefault<z.ZodEnum<{
204
- allow: "allow";
205
237
  deny: "deny";
238
+ allow: "allow";
206
239
  }>>;
207
240
  flag_destructive: z.ZodOptional<z.ZodEnum<{
208
241
  require_approval: "require_approval";
@@ -238,8 +271,8 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
238
271
  }, z.core.$strict>]>>>;
239
272
  }, z.core.$strict>;
240
273
  action: z.ZodEnum<{
241
- allow: "allow";
242
274
  deny: "deny";
275
+ allow: "allow";
243
276
  require_approval: "require_approval";
244
277
  rate_limit: "rate_limit";
245
278
  spend_limit: "spend_limit";
@@ -285,8 +318,8 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
285
318
  }, z.core.$strict>>>;
286
319
  install: z.ZodOptional<z.ZodObject<{
287
320
  default: z.ZodDefault<z.ZodEnum<{
288
- allow: "allow";
289
321
  deny: "deny";
322
+ allow: "allow";
290
323
  }>>;
291
324
  rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
292
325
  name: z.ZodOptional<z.ZodString>;
@@ -315,6 +348,11 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
315
348
  log: "log";
316
349
  block: "block";
317
350
  }>>;
351
+ tool_revalidation: z.ZodOptional<z.ZodObject<{
352
+ enabled: z.ZodDefault<z.ZodBoolean>;
353
+ interval: z.ZodDefault<z.ZodString>;
354
+ max_advertised_ttl: z.ZodOptional<z.ZodString>;
355
+ }, z.core.$strict>>;
318
356
  hot_reload: z.ZodOptional<z.ZodBoolean>;
319
357
  }, z.core.$strict>>;
320
358
  budgets: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -358,8 +396,8 @@ declare const helioConfigSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
358
396
  approval: z.ZodPrefault<z.ZodObject<{
359
397
  timeout: z.ZodDefault<z.ZodString>;
360
398
  default_on_timeout: z.ZodDefault<z.ZodEnum<{
361
- allow: "allow";
362
399
  deny: "deny";
400
+ allow: "allow";
363
401
  }>>;
364
402
  channels: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
365
403
  type: z.ZodLiteral<"slack">;
@@ -406,6 +444,8 @@ type HelioConfig = z.infer<typeof helioConfigSchema>;
406
444
  type ApprovalChannel$1 = z.infer<typeof approvalChannelSchema>;
407
445
  /** The policies section of the config. */
408
446
  type PoliciesConfig = z.infer<typeof policiesSchema>;
447
+ /** A single identity source from `session.identity`. */
448
+ type SessionIdentitySource = z.infer<typeof sessionIdentitySourceSchema>;
409
449
  /** A single named budget from the `budgets` array (issue #14). */
410
450
  type BudgetConfig = z.infer<typeof budgetSchema>;
411
451
  /** The `budgets` section of the config. */
@@ -513,6 +553,12 @@ interface CompiledLimits {
513
553
  readonly key?: 'tool' | 'agent' | 'session' | 'sender_id';
514
554
  readonly maxSpend?: CompiledSpendLimit;
515
555
  }
556
+ /** Compiled tool revalidation settings with durations as milliseconds. */
557
+ interface CompiledToolRevalidation {
558
+ readonly enabled: boolean;
559
+ readonly intervalMs: number;
560
+ readonly maxAdvertisedTtlMs: number;
561
+ }
516
562
  /** Policy action types. */
517
563
  type PolicyAction = 'allow' | 'deny' | 'require_approval' | 'rate_limit' | 'spend_limit' | 'dry_run';
518
564
  /** A fully compiled policy rule ready for engine evaluation. */
@@ -576,6 +622,8 @@ interface CompiledPolicy {
576
622
  * at the use site — conservative by default.
577
623
  */
578
624
  readonly onToolDrift?: 'block' | 'require_approval' | 'log';
625
+ /** Always set by compilePolicies; absent only in hand-built fixtures (treated as disabled). */
626
+ readonly toolRevalidation?: CompiledToolRevalidation;
579
627
  readonly rules: readonly CompiledPolicyRule[];
580
628
  /** Install-time policy (issue #13 — deny_install). Undefined ⇒ observational. */
581
629
  readonly install?: CompiledInstallPolicy;
@@ -679,15 +727,63 @@ interface JsonRpcRequest {
679
727
  method: string;
680
728
  params?: unknown;
681
729
  }
730
+ /** Resolved governance session identity (issue #218). */
731
+ interface ResolvedSession {
732
+ readonly id: string;
733
+ /** Strategy that produced it — recorded in the audit trail. */
734
+ readonly source: 'header' | 'meta' | 'legacy_header' | 'transport';
735
+ }
682
736
  /** A parsed MCP request enriched with session context. */
683
737
  interface McpRequest extends JsonRpcRequest {
684
- /** MCP session ID extracted from the `Mcp-Session-Id` header. */
685
- sessionId?: string;
738
+ /** Proxy-resolved governance session identity (undefined when no strategy matched). */
739
+ session?: ResolvedSession;
740
+ /** Verbatim Mcp-Session-Id from the wire. Transport relay ONLY — never governance. */
741
+ transportSessionId?: string;
742
+ /**
743
+ * The client's verbatim MCP-Protocol-Version wire claim (issue #219) —
744
+ * captured raw, with no normalization, for the audit trail. It is the
745
+ * CLIENT'S claim, not the upstream era. Since issue #226 the
746
+ * streamable-http route validates header/body agreement before
747
+ * forwarding, so on a FORWARDED request a claim of `2026-07-28` has
748
+ * passed the agreement door; tier-2 claims (legacy, unknown, malformed)
749
+ * and notification claims remain unvalidated as captured. Streamable
750
+ * HTTP only: the header postdates the deprecated SSE transport, and
751
+ * stdio has no headers, so both leave it unset.
752
+ */
753
+ protocolVersion?: string;
686
754
  /** Per-request headers to forward to upstream (e.g. Authorization, X-* headers). */
687
755
  headers?: Record<string, string>;
688
756
  /** Abort signal tied to the downstream client request lifecycle. */
689
757
  signal?: AbortSignal;
690
758
  }
759
+ /**
760
+ * An inbound request rejected by the header/body agreement door (issue
761
+ * #226), as protocol facts: what the body said, what the headers claimed,
762
+ * and why they disagree. Deliberately camelCase and transport-free (no Hono
763
+ * types, no audit field names) — the audit-record mapping lives solely in
764
+ * `buildHeaderMismatchAuditRecord` on the audit side.
765
+ */
766
+ interface HeaderMismatchRejection {
767
+ /** Human-readable mismatch reason; echoed values are display-capped. */
768
+ readonly reason: string;
769
+ /** The body's JSON-RPC method. */
770
+ readonly method: string;
771
+ /** The body's params, verbatim as parsed. */
772
+ readonly params?: unknown;
773
+ /**
774
+ * The body's name-bearing string field (`params.name` / `params.uri`),
775
+ * when the method defines one and the body carries a string value.
776
+ */
777
+ readonly bodyName?: string;
778
+ /** The client's verbatim MCP-Protocol-Version wire claim, if any. */
779
+ readonly protocolVersion?: string;
780
+ /** The inbound marker headers that were present, verbatim as received. */
781
+ readonly headers: Readonly<Record<string, string>>;
782
+ /** Proxy-resolved governance session identity, when a strategy matched. */
783
+ readonly session?: ResolvedSession;
784
+ /** Time from route-handler entry to rejection, in milliseconds. */
785
+ readonly durationMs: number;
786
+ }
691
787
  /** The response returned by an MCP forwarder. */
692
788
  interface McpResponse {
693
789
  status: number;
@@ -724,6 +820,13 @@ interface ServerHandle {
724
820
  interface CreateAppOptions {
725
821
  /** Slack interactive action handler (mounted at /slack/actions). */
726
822
  slackActionApp?: Hono;
823
+ /**
824
+ * Recorder for inbound header/body agreement rejections (issue #226) on
825
+ * the streamable-http route. Enforcement does not depend on it — with no
826
+ * recorder the request is still rejected and no record is written (the
827
+ * library-embedding posture `missing_tool_name` also takes).
828
+ */
829
+ onHeaderMismatch?: (rejection: HeaderMismatchRejection) => void;
727
830
  }
728
831
  /**
729
832
  * Create a Hono app configured with the MCP proxy routes.
@@ -748,6 +851,12 @@ declare function startServer(app: Hono, config: HelioConfig): ServerHandle;
748
851
  */
749
852
  declare function startSidebandServer(app: Hono, port: number, host?: string): ServerHandle;
750
853
 
854
+ /**
855
+ * `upstream.protocol_version`: `auto` probes and caches; a dated pin is a
856
+ * constant — never probed, never cached, never cleared.
857
+ */
858
+ type UpstreamProtocolVersionPin = 'auto' | '2025-06-18' | '2026-07-28';
859
+
751
860
  interface StreamableHttpForwarderOptions {
752
861
  /** The upstream MCP server URL (e.g. "http://localhost:8080/mcp"). */
753
862
  url: string;
@@ -755,6 +864,8 @@ interface StreamableHttpForwarderOptions {
755
864
  headers?: Record<string, string>;
756
865
  /** Maximum time to wait for an upstream request before aborting. */
757
866
  requestTimeoutMs?: number;
867
+ /** `upstream.protocol_version`: `auto` (default) probes; a dated pin skips it. */
868
+ protocolVersion?: UpstreamProtocolVersionPin;
758
869
  }
759
870
  /**
760
871
  * Spec-compliant upstream MCP Streamable HTTP client.
@@ -775,11 +886,35 @@ declare class StreamableHttpForwarder implements McpForwarder {
775
886
  /** Lifecycle parity with sse/stdio. */
776
887
  close(): Promise<void>;
777
888
  forward(request: McpRequest): Promise<ForwardResult>;
889
+ /**
890
+ * The dual-era bridge (relay leg, modern era): a modern-only server
891
+ * answers the retired `initialize` handshake with 404/-32601, so Helio
892
+ * synthesizes the legacy InitializeResult locally from the upstream's own
893
+ * probe-time DiscoverResult. No `mcp-session-id` response header — the
894
+ * legacy spec permits sessionless servers, and the downstream stays
895
+ * sessionless. The synthesized protocolVersion is always the current
896
+ * legacy revision, even for a client that offered an older one.
897
+ */
898
+ private synthesizeInitializeResult;
899
+ /**
900
+ * The modern upstream removed `notifications/initialized`; answer the
901
+ * same minimal success envelope the SSE-notification path returns.
902
+ */
903
+ private swallowInitializedNotification;
904
+ /**
905
+ * The relay-side era falsification door (issue #219): a legacy-leg relay whose answer only a
906
+ * modern server gives clears the cached legacy era (the manager no-ops on
907
+ * pins, uncached eras, and cached modern). The response still flows to the
908
+ * client unchanged — no in-place retry.
909
+ */
910
+ private inspectLegacyRelayOutcome;
778
911
  /**
779
912
  * Helio-internal execution path (startup prime / internal maintenance) that
780
913
  * may borrow the proxy-managed internal session.
781
914
  */
782
915
  forwardInternal(request: McpRequest): Promise<ForwardResult>;
916
+ /** Drop the managed internal session AND the cached era; next internal call re-probes. */
917
+ resetInternalSession(): void;
783
918
  private send;
784
919
  }
785
920
 
@@ -939,8 +1074,14 @@ interface AuditRecord {
939
1074
  readonly id: string;
940
1075
  /** ISO 8601 timestamp of when the tool call was received by the proxy. */
941
1076
  readonly timestamp: string;
942
- /** MCP session ID from the Mcp-Session-Id header, if present. */
1077
+ /** Resolved governance session id, or the adapter-supplied id on sideband records. */
943
1078
  readonly session_id: string | null;
1079
+ /**
1080
+ * Which identity strategy produced session_id (issue #218): `header`,
1081
+ * `meta`, `legacy_header`, or `transport` on the MCP door, `sideband` for
1082
+ * adapter-supplied ids. Null when no session resolved.
1083
+ */
1084
+ readonly session_source: string | null;
944
1085
  /** Agent identifier from config or request header, if present. */
945
1086
  readonly agent_id: string | null;
946
1087
  /** Runtime environment label configured on proxy startup, if set. */
@@ -1007,6 +1148,14 @@ interface AuditRecord {
1007
1148
  * `match.metadata.*` (#13) and the dashboard metadata columns (#16).
1008
1149
  */
1009
1150
  readonly metadata: Record<string, unknown> | null;
1151
+ /**
1152
+ * The client's verbatim MCP-Protocol-Version wire claim on the inbound
1153
+ * request (issue #219) — the CLIENT'S claim, not the upstream era, and not
1154
+ * a validated fact. Recorded uncapped: truncation would manufacture a
1155
+ * false claim in an audit record. Null when the request carried no header
1156
+ * and on records with no MCP wire (drift events, sideband records).
1157
+ */
1158
+ readonly protocol_version: string | null;
1010
1159
  /** ISO 8601 timestamp of when the record was persisted. */
1011
1160
  readonly created_at: string;
1012
1161
  }
@@ -2032,11 +2181,28 @@ declare class SpendLimiter {
2032
2181
  close(): void;
2033
2182
  }
2034
2183
 
2184
+ declare const GATED_SESSION: unique symbol;
2185
+ /** A session bucket value that provably passed the identity gate. */
2186
+ type GatedSession = string & {
2187
+ readonly [GATED_SESSION]: true;
2188
+ };
2189
+ declare const GATED_CHARGES: unique symbol;
2190
+ /** Budget charges that provably passed the session engagement check. */
2191
+ type GatedCharges = readonly BudgetCharge[] & {
2192
+ readonly [GATED_CHARGES]: true;
2193
+ };
2194
+
2035
2195
  /** Everything the engine needs about one tool call to resolve its charges. */
2036
2196
  interface BudgetChargeContext {
2037
2197
  readonly toolName: string;
2038
2198
  readonly toolArguments: Record<string, unknown> | undefined;
2039
- readonly sessionId: string | null;
2199
+ /**
2200
+ * Gate-minted session bucket value (issue #218), or null when identity is
2201
+ * unresolved — `bucketKey` builds `session:unknown` from null, which is
2202
+ * legal ONLY because `gateBudgetCharges` denies unresolved session-keyed
2203
+ * engagement before `peekAll`/`recordAll` can run.
2204
+ */
2205
+ readonly sessionId: GatedSession | null;
2040
2206
  /** Adapter-supplied sender id (sideband only); null on the MCP path. */
2041
2207
  readonly senderId: string | null;
2042
2208
  }
@@ -2220,8 +2386,12 @@ declare class BudgetEngine {
2220
2386
  charges: BudgetCharge[];
2221
2387
  failures: BudgetChargeFailure[];
2222
2388
  };
2223
- /** Check every charge without mutating. All-or-nothing: one deny flips `allowed`. */
2224
- peekAll(charges: readonly BudgetCharge[]): {
2389
+ /**
2390
+ * Check every charge without mutating. All-or-nothing: one deny flips
2391
+ * `allowed`. Accepts only gate-branded charges (issue #218) — a caller
2392
+ * cannot peek budget state without having run the session engagement check.
2393
+ */
2394
+ peekAll(charges: GatedCharges): {
2225
2395
  allowed: boolean;
2226
2396
  entries: BudgetPeekEntry[];
2227
2397
  };
@@ -2232,7 +2402,7 @@ declare class BudgetEngine {
2232
2402
  * Recording is unconditional past the sink (an approved overage
2233
2403
  * legitimately pushes a bucket past its limit).
2234
2404
  */
2235
- recordAll(charges: readonly BudgetCharge[], meta: BudgetCommitMeta): BudgetPeekEntry[];
2405
+ recordAll(charges: GatedCharges, meta: BudgetCommitMeta): BudgetPeekEntry[];
2236
2406
  /**
2237
2407
  * Fire one `onBreach` event per breached entry. Called by the doors at the
2238
2408
  * moment a peek outcome actually denies the call or raises the composite
@@ -2329,6 +2499,18 @@ declare class BudgetEngine {
2329
2499
  private snapshot;
2330
2500
  }
2331
2501
 
2502
+ /** Compiled session config, wired into the route factories at startup. */
2503
+ interface CompiledSessionIdentity {
2504
+ /** The configured identity chain, in order; first match wins. */
2505
+ readonly sources: readonly SessionIdentitySource[];
2506
+ readonly onUnresolved: 'deny' | 'anonymous';
2507
+ /**
2508
+ * Human-readable strategy list (e.g. `header "x-helio-session-id",
2509
+ * legacy_header`) — deny messages name the strategies that were tried.
2510
+ */
2511
+ readonly strategySummary: string;
2512
+ }
2513
+
2332
2514
  /** Options for constructing a GovernedForwarder. */
2333
2515
  interface GovernedForwarderOptions {
2334
2516
  /** The current environment label (e.g. "production", "staging"). */
@@ -2345,6 +2527,12 @@ interface GovernedForwarderOptions {
2345
2527
  spendLimiter?: SpendLimiter;
2346
2528
  /** Budget engine for named cross-tool budgets (issue #14). */
2347
2529
  budgetEngine?: BudgetEngine;
2530
+ /**
2531
+ * Compiled session identity config (issue #218): the `on_unresolved` mode
2532
+ * and the strategy summary deny messages name. Defaults to the schema
2533
+ * default chain (deny mode) for direct/library construction.
2534
+ */
2535
+ session?: CompiledSessionIdentity;
2348
2536
  }
2349
2537
  /** Result of attempting to prime the tool annotation cache. */
2350
2538
  interface AnnotationCachePrimeResult {
@@ -2367,6 +2555,7 @@ declare class GovernedForwarder implements McpForwarder {
2367
2555
  private readonly inner;
2368
2556
  private policy;
2369
2557
  private readonly environment;
2558
+ private readonly session;
2370
2559
  private readonly auditWriter;
2371
2560
  private readonly evidenceStore;
2372
2561
  private readonly approvalRouter;
@@ -2406,6 +2595,20 @@ declare class GovernedForwarder implements McpForwarder {
2406
2595
  */
2407
2596
  primeAnnotationCache(): Promise<AnnotationCachePrimeResult>;
2408
2597
  forward(request: McpRequest): Promise<ForwardResult>;
2598
+ /**
2599
+ * Clamp an over-long `result.ttlMs` on a `tools/list` response to
2600
+ * `policies.tool_revalidation.max_advertised_ttl` (issue #221, D7).
2601
+ *
2602
+ * Downward-only: a `ttlMs` at or below the cap is left untouched, and a
2603
+ * response with no `ttlMs` never gains one — Helio does not manufacture a
2604
+ * cache hint the upstream never advertised. Non-numeric values are left
2605
+ * alone rather than coerced. `cacheScope` passes through untouched: Helio
2606
+ * baselines and vouches for tool *definitions* only, and its own
2607
+ * `tools/list` view is not caller-varying, so it has no basis to alter a
2608
+ * scope hint the upstream set. No-op when tool revalidation is disabled
2609
+ * (including hand-built `CompiledPolicy` fixtures that omit the field).
2610
+ */
2611
+ private clampCacheHints;
2409
2612
  /**
2410
2613
  * Apply a tools/list response to the definition cache and surface any
2411
2614
  * drift: console warning + immediate audit record per event. Single entry
@@ -2469,14 +2672,20 @@ declare class GovernedForwarder implements McpForwarder {
2469
2672
  * or consuming any resources. Returns an MCP-compliant synthetic response.
2470
2673
  */
2471
2674
  private handleDryRun;
2472
- /** Construct a limit bucket key based on the configured key type. */
2675
+ /**
2676
+ * Construct a non-session limit bucket key. Session keys are deliberately
2677
+ * NOT built here: they come only from the gate module's `sessionLimitKey`,
2678
+ * whose `GatedSession` parameter makes skipping the identity gate a
2679
+ * compile error (issue #218) — call sites branch on `key === 'session'`.
2680
+ */
2473
2681
  private buildLimitKey;
2474
2682
  /**
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.
2683
+ * Gate a session-keyed limit at its key-build site (issue #218). Returns
2684
+ * the bucket key, or null when identity is unresolved under deny mode —
2685
+ * the caller denies (enforce) or reports the marker (dry-run).
2478
2686
  */
2479
- private buildSpendLimitKey;
2687
+ private gateSessionLimitKey;
2688
+ private makeSessionUnresolvedResult;
2480
2689
  private writeAuditRecord;
2481
2690
  private makeDriftBlockResult;
2482
2691
  private makeDenyResult;
@@ -2611,6 +2820,13 @@ interface GovernanceServiceOptions {
2611
2820
  readonly spendLimiter?: SpendLimiter;
2612
2821
  /** Budget engine for named cross-tool budgets (issue #14). */
2613
2822
  readonly budgetEngine?: BudgetEngine;
2823
+ /**
2824
+ * Compiled session identity config (issue #218): the sideband door gets
2825
+ * the SAME on_unresolved policy as the MCP door — an adapter omitting
2826
+ * session_id while a session-keyed control is engaged is denied under
2827
+ * `deny`, pools under `anonymous`. Defaults to the schema default chain.
2828
+ */
2829
+ readonly session?: CompiledSessionIdentity;
2614
2830
  readonly auditWriter?: AuditWriter;
2615
2831
  /** Default approval timeout (ms) when a rule sets none. */
2616
2832
  readonly approvalTimeoutMs?: number;
@@ -2630,6 +2846,7 @@ interface GovernanceServiceOptions {
2630
2846
  declare class GovernanceService {
2631
2847
  private policy;
2632
2848
  private readonly environment;
2849
+ private readonly session;
2633
2850
  private readonly evidenceStore;
2634
2851
  private readonly approvalRouter;
2635
2852
  private readonly rateLimiter;
@@ -2789,6 +3006,22 @@ declare class GovernanceConfigError extends Error {
2789
3006
  constructor(message: string);
2790
3007
  }
2791
3008
 
3009
+ /**
3010
+ * Map a header/body agreement rejection (issue #226) onto the audit record
3011
+ * shape, mirroring the `missing_tool_name` precedent: `policy_decision:
3012
+ * 'rejected'`, no rule evaluated, nothing forwarded, written by the caller
3013
+ * via `pushImmediate`. The snake_case mapping lives here alone — the
3014
+ * rejection payload itself stays camelCase protocol facts (`mcp/types.ts`).
3015
+ *
3016
+ * `record_kind` stays `'tool_call'` for the whole class, non-tool methods
3017
+ * included: it is the MCP-door request bucket, and `block_reason` is the
3018
+ * query key. `tool_name` carries the body's name-bearing field (the body
3019
+ * truth an investigator filters for) when the method has one, else the
3020
+ * reserved `'<header_mismatch>'` sentinel — either way `top_tools` already
3021
+ * excludes rejected records, so these rows never pollute tool rankings.
3022
+ */
3023
+ declare function buildHeaderMismatchAuditRecord(rejection: HeaderMismatchRejection, environment?: string): Omit<AuditRecord, 'id' | 'created_at'>;
3024
+
2792
3025
  /** @internal Exported for testing only. */
2793
3026
  declare class QueueChannel implements ApprovalChannel {
2794
3027
  readonly type = "dashboard";
@@ -2915,6 +3148,10 @@ interface ActionEvent {
2915
3148
  readonly block_reason: string | null;
2916
3149
  readonly approval_status: string | null;
2917
3150
  readonly session_id: string | null;
3151
+ /** Identity strategy that produced session_id (issue #218), or null. */
3152
+ readonly session_source: string | null;
3153
+ /** The client's verbatim MCP-Protocol-Version wire claim (issue #219), or null. */
3154
+ readonly protocol_version: string | null;
2918
3155
  readonly agent_id: string | null;
2919
3156
  readonly environment: string | null;
2920
3157
  readonly timestamp: string;
@@ -3061,4 +3298,4 @@ interface DashboardAppOptions {
3061
3298
  */
3062
3299
  declare function createDashboardApp(deps: DashboardAppDeps, options?: DashboardAppOptions): Hono;
3063
3300
 
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 };
3301
+ 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 HeaderMismatchRejection, 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, buildHeaderMismatchAuditRecord, compileBudgets, compilePolicies, createApp, createApprovalApp, createChannels, createDashboardApp, createSidebandApp, createSlackActionApp, evaluatePolicy, loadConfig, matchRule, startServer, startSidebandServer };