@gethelio/proxy 0.13.1 → 0.14.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.js CHANGED
@@ -985,7 +985,7 @@ function interpolateTracked(value, env, path, out) {
985
985
  }
986
986
  return value;
987
987
  }
988
- async function loadConfigWithMeta(filePath, env) {
988
+ async function readConfigSource(filePath) {
989
989
  let bytes;
990
990
  try {
991
991
  bytes = await readFile(filePath);
@@ -993,10 +993,12 @@ async function loadConfigWithMeta(filePath, env) {
993
993
  throw new ConfigError(`Cannot read config file: ${filePath}`);
994
994
  }
995
995
  const sha256 = createHash("sha256").update(bytes).digest("hex");
996
- const raw = bytes.toString("utf-8");
996
+ return { raw: bytes.toString("utf-8"), sha256 };
997
+ }
998
+ function parseConfigSource(source, filePath, env) {
997
999
  let parsed;
998
1000
  try {
999
- parsed = yaml.load(raw);
1001
+ parsed = yaml.load(source.raw);
1000
1002
  } catch (err) {
1001
1003
  const message = err instanceof Error ? err.message : String(err);
1002
1004
  throw new ConfigError(`YAML parse error in ${filePath}: ${message}`);
@@ -1014,12 +1016,27 @@ async function loadConfigWithMeta(filePath, env) {
1014
1016
  details
1015
1017
  );
1016
1018
  }
1017
- return { config: result.data, sha256, interpolatedPaths };
1019
+ return { config: result.data, interpolatedPaths };
1020
+ }
1021
+ async function loadConfigWithMeta(filePath, env) {
1022
+ const source = await readConfigSource(filePath);
1023
+ const { config, interpolatedPaths } = parseConfigSource(source, filePath, env);
1024
+ return { config, sha256: source.sha256, interpolatedPaths };
1018
1025
  }
1019
1026
  async function loadConfig(filePath, env) {
1020
1027
  return (await loadConfigWithMeta(filePath, env)).config;
1021
1028
  }
1022
1029
 
1030
+ // src/config/reload-outcomes.ts
1031
+ var POLICY_RELOAD_OUTCOMES = [
1032
+ "applied",
1033
+ "rejected_invalid",
1034
+ "rejected_unroutable",
1035
+ "rejected_budget_flush",
1036
+ "rejected_pinned",
1037
+ "watch_failed"
1038
+ ];
1039
+
1023
1040
  // src/config/watcher.ts
1024
1041
  import { watch } from "chokidar";
1025
1042
 
@@ -7303,6 +7320,7 @@ function clampInt(value, fallback, min, max) {
7303
7320
  // src/audit/store.ts
7304
7321
  var DRIFT_EVENT_DECISIONS_SQL = "('tool_drift', 'tool_drift_reverted')";
7305
7322
  var NON_TOOL_DECISIONS_SQL = "('tool_drift', 'tool_drift_reverted', 'rejected')";
7323
+ var POLICY_RELOAD_KIND_SQL = "'policy_reload'";
7306
7324
  var EXPORT_MAX_RECORDS = 1e4;
7307
7325
  var LIST_MAX_PAGE_SIZE = 1e3;
7308
7326
  var CREATE_TABLE_DDL = `
@@ -7336,7 +7354,8 @@ CREATE TABLE IF NOT EXISTS audit_records (
7336
7354
  metadata TEXT,
7337
7355
  protocol_version TEXT,
7338
7356
  created_at TEXT NOT NULL,
7339
- upstream TEXT
7357
+ upstream TEXT,
7358
+ config_sha256 TEXT
7340
7359
  );
7341
7360
  `;
7342
7361
  var CREATE_INDEX_DDL = `
@@ -7349,6 +7368,7 @@ CREATE INDEX IF NOT EXISTS idx_audit_upstream_status_created_at ON audit_records
7349
7368
  CREATE INDEX IF NOT EXISTS idx_audit_record_kind ON audit_records (record_kind);
7350
7369
  CREATE INDEX IF NOT EXISTS idx_audit_origin ON audit_records (origin);
7351
7370
  CREATE INDEX IF NOT EXISTS idx_audit_upstream ON audit_records (upstream);
7371
+ CREATE INDEX IF NOT EXISTS idx_audit_config_sha256 ON audit_records (config_sha256);
7352
7372
  `;
7353
7373
  var INSERT_SQL = `
7354
7374
  INSERT INTO audit_records (
@@ -7358,7 +7378,7 @@ INSERT INTO audit_records (
7358
7378
  upstream_http_status,
7359
7379
  total_duration_ms, approval_wait_ms, proxy_compute_ms,
7360
7380
  flagged_destructive, dry_run, record_kind, origin, metadata, protocol_version, created_at,
7361
- upstream
7381
+ upstream, config_sha256
7362
7382
  ) VALUES (
7363
7383
  @id, @timestamp, @session_id, @session_source, @agent_id, @environment, @tool_name, @tool_input,
7364
7384
  @policy_decision, @block_reason, @matched_rule, @matched_rule_index, @evidence_chain, @approval_status,
@@ -7366,7 +7386,7 @@ INSERT INTO audit_records (
7366
7386
  @upstream_http_status,
7367
7387
  @total_duration_ms, @approval_wait_ms, @proxy_compute_ms,
7368
7388
  @flagged_destructive, @dry_run, @record_kind, @origin, @metadata, @protocol_version, @created_at,
7369
- @upstream
7389
+ @upstream, @config_sha256
7370
7390
  )
7371
7391
  `;
7372
7392
  var REQUIRED_AUDIT_COLUMNS = [
@@ -7386,10 +7406,14 @@ var REQUIRED_AUDIT_COLUMNS = [
7386
7406
  // Same clean break, same unreleased cycle (issue #219): released users see
7387
7407
  // ONE break, at v0.12.0.
7388
7408
  "protocol_version",
7389
- // The one ratified exception to the clean break (issue #292): a
7390
- // v0.12.0-complete database missing ONLY this column is migrated in place
7391
- // by migrateAuditUpstreamColumn instead of failing the assertion.
7392
- "upstream"
7409
+ // The ratified exception to the clean break (issue #292): a database that
7410
+ // is complete except for this column is migrated in place by
7411
+ // migrateAdditiveAuditColumns instead of failing the assertion.
7412
+ "upstream",
7413
+ // The second additive column under the same exception (issue #341): a
7414
+ // v0.13 database missing only this one, or a v0.12.0 database missing
7415
+ // both, migrates in place; anything older still clean-breaks.
7416
+ "config_sha256"
7393
7417
  ];
7394
7418
  function deserializeRow(row) {
7395
7419
  return {
@@ -7422,6 +7446,7 @@ function deserializeRow(row) {
7422
7446
  metadata: row.metadata ? JSON.parse(row.metadata) : null,
7423
7447
  protocol_version: row.protocol_version,
7424
7448
  upstream: row.upstream,
7449
+ config_sha256: row.config_sha256,
7425
7450
  created_at: row.created_at
7426
7451
  };
7427
7452
  }
@@ -7502,21 +7527,34 @@ function buildWhereClause(filters) {
7502
7527
  const clause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
7503
7528
  return { clause, params };
7504
7529
  }
7505
- function migrateAuditUpstreamColumn(db) {
7530
+ var ADDITIVE_AUDIT_COLUMNS = [
7531
+ { name: "upstream", ddl: "upstream TEXT" },
7532
+ { name: "config_sha256", ddl: "config_sha256 TEXT" }
7533
+ ];
7534
+ function migrateAdditiveAuditColumns(db) {
7506
7535
  const probe = () => {
7507
7536
  const rows = db.pragma("table_info(audit_records)");
7508
7537
  return new Set(rows.map((row) => row.name));
7509
7538
  };
7510
7539
  const existing = probe();
7511
- const missing = REQUIRED_AUDIT_COLUMNS.filter((name) => !existing.has(name));
7512
- if (missing.length !== 1 || missing[0] !== "upstream") return false;
7513
- try {
7514
- db.exec("ALTER TABLE audit_records ADD COLUMN upstream TEXT");
7515
- } catch (err) {
7516
- if (probe().has("upstream")) return false;
7517
- throw err;
7540
+ const missing = new Set(REQUIRED_AUDIT_COLUMNS.filter((name) => !existing.has(name)));
7541
+ if (missing.size === 0) return [];
7542
+ const additive = new Set(ADDITIVE_AUDIT_COLUMNS.map((column) => column.name));
7543
+ for (const name of missing) {
7544
+ if (!additive.has(name)) return [];
7545
+ }
7546
+ const added = [];
7547
+ for (const column of ADDITIVE_AUDIT_COLUMNS) {
7548
+ if (!missing.has(column.name)) continue;
7549
+ try {
7550
+ db.exec(`ALTER TABLE audit_records ADD COLUMN ${column.ddl}`);
7551
+ } catch (err) {
7552
+ if (probe().has(column.name)) continue;
7553
+ throw err;
7554
+ }
7555
+ added.push(column.name);
7518
7556
  }
7519
- return true;
7557
+ return added;
7520
7558
  }
7521
7559
  function restrictAuditFilePerms(dbPath) {
7522
7560
  if (dbPath === ":memory:" || process.platform === "win32") return;
@@ -7546,8 +7584,8 @@ var AuditStore = class {
7546
7584
  this.retentionMs = parseDuration(options.retention);
7547
7585
  this.includeResponses = options.includeResponses;
7548
7586
  this.db.exec(CREATE_TABLE_DDL);
7549
- if (migrateAuditUpstreamColumn(this.db)) {
7550
- console.error('[helio] Audit DB migrated: added column "upstream"');
7587
+ for (const name of migrateAdditiveAuditColumns(this.db)) {
7588
+ console.error(`[helio] Audit DB migrated: added column "${name}"`);
7551
7589
  }
7552
7590
  this.assertRequiredSchema(options.path);
7553
7591
  this.db.exec(CREATE_INDEX_DDL);
@@ -7658,6 +7696,7 @@ var AuditStore = class {
7658
7696
  metadata: record.metadata ? JSON.stringify(record.metadata) : null,
7659
7697
  protocol_version: record.protocol_version,
7660
7698
  upstream: record.upstream ?? null,
7699
+ config_sha256: record.config_sha256 ?? null,
7661
7700
  created_at: now
7662
7701
  });
7663
7702
  return resolvedId;
@@ -7745,26 +7784,27 @@ var AuditStore = class {
7745
7784
  const totals = this.db.prepare(
7746
7785
  `SELECT
7747
7786
  COUNT(*) as total,
7748
- COALESCE(SUM(CASE WHEN block_reason IS NULL AND policy_decision NOT IN ${DRIFT_EVENT_DECISIONS_SQL} THEN 1 ELSE 0 END), 0) as allowed_total,
7749
- COALESCE(SUM(CASE WHEN block_reason IS NOT NULL THEN 1 ELSE 0 END), 0) as blocked_total,
7750
- COALESCE(SUM(CASE WHEN dry_run = 1 THEN 1 ELSE 0 END), 0) as dry_run_total,
7751
- COALESCE(SUM(CASE WHEN dry_run = 0 THEN 1 ELSE 0 END), 0) as applied_total
7787
+ COALESCE(SUM(CASE WHEN block_reason IS NULL AND policy_decision NOT IN ${DRIFT_EVENT_DECISIONS_SQL} AND record_kind <> ${POLICY_RELOAD_KIND_SQL} THEN 1 ELSE 0 END), 0) as allowed_total,
7788
+ COALESCE(SUM(CASE WHEN block_reason IS NOT NULL AND record_kind <> ${POLICY_RELOAD_KIND_SQL} THEN 1 ELSE 0 END), 0) as blocked_total,
7789
+ COALESCE(SUM(CASE WHEN dry_run = 1 AND record_kind <> ${POLICY_RELOAD_KIND_SQL} THEN 1 ELSE 0 END), 0) as dry_run_total,
7790
+ COALESCE(SUM(CASE WHEN dry_run = 0 AND record_kind <> ${POLICY_RELOAD_KIND_SQL} THEN 1 ELSE 0 END), 0) as applied_total
7752
7791
  FROM audit_records ${clause}`
7753
7792
  ).get(...params);
7793
+ const decisionClause = clause ? `${clause} AND record_kind <> ${POLICY_RELOAD_KIND_SQL}` : `WHERE record_kind <> ${POLICY_RELOAD_KIND_SQL}`;
7754
7794
  const by_decision = this.db.prepare(
7755
7795
  `SELECT policy_decision as decision, COUNT(*) as count
7756
- FROM audit_records ${clause}
7796
+ FROM audit_records ${decisionClause}
7757
7797
  GROUP BY policy_decision
7758
7798
  ORDER BY count DESC`
7759
7799
  ).all(...params);
7760
- const blockedClause = clause ? `${clause} AND block_reason IS NOT NULL` : "WHERE block_reason IS NOT NULL";
7800
+ const blockedClause = clause ? `${clause} AND block_reason IS NOT NULL AND record_kind <> ${POLICY_RELOAD_KIND_SQL}` : `WHERE block_reason IS NOT NULL AND record_kind <> ${POLICY_RELOAD_KIND_SQL}`;
7761
7801
  const by_block_reason = this.db.prepare(
7762
7802
  `SELECT block_reason as reason, COUNT(*) as count
7763
7803
  FROM audit_records ${blockedClause}
7764
7804
  GROUP BY block_reason
7765
7805
  ORDER BY count DESC`
7766
7806
  ).all(...params);
7767
- const toolsClause = clause ? `${clause} AND policy_decision NOT IN ${NON_TOOL_DECISIONS_SQL}` : `WHERE policy_decision NOT IN ${NON_TOOL_DECISIONS_SQL}`;
7807
+ const toolsClause = clause ? `${clause} AND policy_decision NOT IN ${NON_TOOL_DECISIONS_SQL} AND record_kind <> ${POLICY_RELOAD_KIND_SQL}` : `WHERE policy_decision NOT IN ${NON_TOOL_DECISIONS_SQL} AND record_kind <> ${POLICY_RELOAD_KIND_SQL}`;
7768
7808
  const top_tools = this.db.prepare(
7769
7809
  `SELECT tool_name, upstream, COUNT(*) as count
7770
7810
  FROM audit_records ${toolsClause}
@@ -7852,11 +7892,12 @@ var CSV_HEADERS = [
7852
7892
  "record_kind",
7853
7893
  "origin",
7854
7894
  "metadata",
7855
- // Appended LAST (issues #218, #219, #292): positional consumers of the
7856
- // existing columns keep working — new columns always go at the end.
7895
+ // Appended LAST (issues #218, #219, #292, #341): positional consumers of
7896
+ // the existing columns keep working — new columns always go at the end.
7857
7897
  "session_source",
7858
7898
  "protocol_version",
7859
- "upstream"
7899
+ "upstream",
7900
+ "config_sha256"
7860
7901
  ];
7861
7902
  var FORMULA_PREFIXES = /^[=+\-@\t\r]/;
7862
7903
  function csvEscape(value) {
@@ -9833,6 +9874,15 @@ var AuditWriter = class {
9833
9874
  bufferSize;
9834
9875
  onPush;
9835
9876
  onPersist;
9877
+ /**
9878
+ * The hash of the config file in force (issue #341), stamped onto every
9879
+ * record whose builder left `config_sha256` nullish. Seeded at
9880
+ * construction so no record is pushed unstamped before the first
9881
+ * `setConfigSha256`; replaced by the reload path once the new policy is
9882
+ * in force. Null only when the caller never had a hash (library
9883
+ * embeddings without a config file).
9884
+ */
9885
+ configSha256;
9836
9886
  buffer = [];
9837
9887
  timer = null;
9838
9888
  flushSoonTimer = null;
@@ -9842,6 +9892,7 @@ var AuditWriter = class {
9842
9892
  this.bufferSize = options.bufferSize ?? 50;
9843
9893
  this.onPush = options.onPush;
9844
9894
  this.onPersist = options.onPersist;
9895
+ this.configSha256 = options.configSha256 ?? null;
9845
9896
  const intervalMs = options.flushIntervalMs ?? 100;
9846
9897
  if (intervalMs > 0) {
9847
9898
  this.timer = setInterval(() => {
@@ -9862,8 +9913,9 @@ var AuditWriter = class {
9862
9913
  * is scheduled. This keeps request-path latency bounded even under bursty
9863
9914
  * write load.
9864
9915
  */
9865
- push(record, id = randomUUID6()) {
9916
+ push(input, id = randomUUID6()) {
9866
9917
  if (this.closed) return;
9918
+ const record = this.stamp(input);
9867
9919
  this.buffer.push({ id, record });
9868
9920
  this.onPush?.(record, id);
9869
9921
  if (this.buffer.length >= this.bufferSize) {
@@ -9878,12 +9930,30 @@ var AuditWriter = class {
9878
9930
  * A fatal-process crash still invokes the crash-drain hook, which calls
9879
9931
  * `flush()` synchronously before exit.
9880
9932
  */
9881
- pushImmediate(record, id = randomUUID6()) {
9933
+ pushImmediate(input, id = randomUUID6()) {
9882
9934
  if (this.closed) return;
9935
+ const record = this.stamp(input);
9883
9936
  this.buffer.push({ id, record });
9884
9937
  this.onPush?.(record, id);
9885
9938
  this.scheduleFlushSoon();
9886
9939
  }
9940
+ /**
9941
+ * Replace the config hash every later record is stamped with. Called by
9942
+ * the reload path after the new policy is in force, before the reload's
9943
+ * own record is pushed, so the reload record and every call it governs
9944
+ * carry the new hash.
9945
+ */
9946
+ setConfigSha256(hash) {
9947
+ this.configSha256 = hash;
9948
+ }
9949
+ /**
9950
+ * Stamp the active config hash onto a record whose builder left the field
9951
+ * nullish; a record that already carries a hash is passed through. The
9952
+ * stamped record is what `onPush`, the store, and `onPersist` see.
9953
+ */
9954
+ stamp(record) {
9955
+ return record.config_sha256 == null ? { ...record, config_sha256: this.configSha256 } : record;
9956
+ }
9887
9957
  /**
9888
9958
  * Schedule a flush on the next tick, coalescing multiple calls into one.
9889
9959
  */
@@ -9979,6 +10049,25 @@ function buildHeaderMismatchAuditRecord(rejection, environment, upstream) {
9979
10049
  };
9980
10050
  }
9981
10051
 
10052
+ // src/audit/policy-reload.ts
10053
+ import { basename } from "path";
10054
+ import { z as z6 } from "zod";
10055
+ var policyReloadEvidenceSchema = z6.object({
10056
+ outcome: z6.enum(POLICY_RELOAD_OUTCOMES),
10057
+ config_path: z6.string(),
10058
+ sha256_before: z6.string(),
10059
+ sha256_after: z6.string().nullable(),
10060
+ rule_count_before: z6.number(),
10061
+ rule_count_after: z6.number().nullable(),
10062
+ default_action_before: z6.enum(["allow", "deny"]),
10063
+ default_action_after: z6.enum(["allow", "deny"]).nullable(),
10064
+ budget_count_before: z6.number(),
10065
+ budget_count_after: z6.number().nullable(),
10066
+ rules_removed: z6.array(z6.string()),
10067
+ restart_required_paths: z6.array(z6.string()),
10068
+ error: z6.string().nullable()
10069
+ }).strict();
10070
+
9982
10071
  // src/approval/queue.ts
9983
10072
  import { randomUUID as randomUUID7 } from "crypto";
9984
10073
  var ApprovalQueue = class {
@@ -10638,7 +10727,7 @@ function createChannels(channels) {
10638
10727
  // src/approval/slack-actions.ts
10639
10728
  import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
10640
10729
  import { Hono as Hono6 } from "hono";
10641
- import { z as z6 } from "zod";
10730
+ import { z as z7 } from "zod";
10642
10731
  var MAX_TIMESTAMP_AGE_S = 300;
10643
10732
  var REJECTION_LOG_WINDOW_MS = 6e4;
10644
10733
  var REJECTION_LOG_SAMPLE_EVERY = 25;
@@ -10704,12 +10793,12 @@ function verifySlackSignature(secrets, timestamp, rawBody, signature) {
10704
10793
  }
10705
10794
  return false;
10706
10795
  }
10707
- var slackActionPayloadSchema = z6.object({
10708
- type: z6.string(),
10709
- user: z6.object({ id: z6.string(), username: z6.string() }),
10710
- actions: z6.array(z6.object({ action_id: z6.string() })),
10711
- channel: z6.object({ id: z6.string() }),
10712
- message: z6.object({ ts: z6.string() })
10796
+ var slackActionPayloadSchema = z7.object({
10797
+ type: z7.string(),
10798
+ user: z7.object({ id: z7.string(), username: z7.string() }),
10799
+ actions: z7.array(z7.object({ action_id: z7.string() })),
10800
+ channel: z7.object({ id: z7.string() }),
10801
+ message: z7.object({ ts: z7.string() })
10713
10802
  });
10714
10803
  function parseActionPayload(rawBody) {
10715
10804
  try {
@@ -10839,17 +10928,17 @@ function createSlackActionApp(options) {
10839
10928
 
10840
10929
  // src/approval/api.ts
10841
10930
  import { Hono as Hono7 } from "hono";
10842
- import { z as z7 } from "zod";
10843
- var approveBody = z7.object({
10844
- approved_by: z7.string().min(1)
10931
+ import { z as z8 } from "zod";
10932
+ var approveBody = z8.object({
10933
+ approved_by: z8.string().min(1)
10845
10934
  });
10846
- var denyBody = z7.object({
10847
- denied_by: z7.string().min(1),
10848
- reason: z7.string().optional()
10935
+ var denyBody = z8.object({
10936
+ denied_by: z8.string().min(1),
10937
+ reason: z8.string().optional()
10849
10938
  });
10850
- var breakGlassBody = z7.object({
10851
- approved_by: z7.string().min(1),
10852
- reason: z7.string().min(1)
10939
+ var breakGlassBody = z8.object({
10940
+ approved_by: z8.string().min(1),
10941
+ reason: z8.string().min(1)
10853
10942
  });
10854
10943
  var APPROVAL_STATUSES = [
10855
10944
  "pending",
@@ -10862,18 +10951,18 @@ var APPROVAL_STATUSES = [
10862
10951
  "cancelled"
10863
10952
  ];
10864
10953
  var approvalStatusSet = new Set(APPROVAL_STATUSES);
10865
- var listApprovalsQuery = z7.object({
10866
- status: z7.preprocess(
10954
+ var listApprovalsQuery = z8.object({
10955
+ status: z8.preprocess(
10867
10956
  (value) => typeof value === "string" && approvalStatusSet.has(value) ? value : void 0,
10868
- z7.enum(APPROVAL_STATUSES).optional()
10957
+ z8.enum(APPROVAL_STATUSES).optional()
10869
10958
  ),
10870
- limit: z7.preprocess(
10959
+ limit: z8.preprocess(
10871
10960
  (value) => clampInt(typeof value === "string" ? value : void 0, 50, 1, 1e3),
10872
- z7.number().int()
10961
+ z8.number().int()
10873
10962
  ),
10874
- offset: z7.preprocess(
10963
+ offset: z8.preprocess(
10875
10964
  (value) => clampInt(typeof value === "string" ? value : void 0, 0, 0, Number.MAX_SAFE_INTEGER),
10876
- z7.number().int()
10965
+ z8.number().int()
10877
10966
  )
10878
10967
  });
10879
10968
  function createApprovalApp(router, queue, options) {
@@ -11018,7 +11107,7 @@ import { join } from "path";
11018
11107
  import { randomBytes as randomBytes2, randomUUID as randomUUID8 } from "crypto";
11019
11108
  import { Hono as Hono8 } from "hono";
11020
11109
  import { HTTPException as HTTPException2 } from "hono/http-exception";
11021
- import { z as z8 } from "zod";
11110
+ import { z as z9 } from "zod";
11022
11111
  import { cors } from "hono/cors";
11023
11112
  import { serveStatic } from "@hono/node-server/serve-static";
11024
11113
  import { streamSSE } from "hono/streaming";
@@ -11116,24 +11205,24 @@ var DashboardSessionStore = class {
11116
11205
  };
11117
11206
 
11118
11207
  // src/dashboard/api.ts
11119
- var optionalQueryString = z8.preprocess(
11208
+ var optionalQueryString = z9.preprocess(
11120
11209
  (value) => typeof value === "string" && value.length > 0 ? value : void 0,
11121
- z8.string().optional()
11210
+ z9.string().optional()
11122
11211
  );
11123
- var optionalQueryInt = z8.preprocess((value) => {
11212
+ var optionalQueryInt = z9.preprocess((value) => {
11124
11213
  if (typeof value !== "string" || value.length === 0) return void 0;
11125
11214
  const parsed = Number.parseInt(value, 10);
11126
11215
  return Number.isFinite(parsed) ? parsed : void 0;
11127
- }, z8.number().int().optional());
11128
- var queryBoolean = z8.preprocess(
11216
+ }, z9.number().int().optional());
11217
+ var queryBoolean = z9.preprocess(
11129
11218
  (value) => value === "true" ? true : value === "false" ? false : void 0,
11130
- z8.boolean().optional()
11219
+ z9.boolean().optional()
11131
11220
  );
11132
- var clampedQueryInt = (fallback, min, max) => z8.preprocess(
11221
+ var clampedQueryInt = (fallback, min, max) => z9.preprocess(
11133
11222
  (value) => clampInt(typeof value === "string" ? value : void 0, fallback, min, max),
11134
- z8.number().int()
11223
+ z9.number().int()
11135
11224
  );
11136
- var feedQuerySchema = z8.object({
11225
+ var feedQuerySchema = z9.object({
11137
11226
  limit: clampedQueryInt(50, 1, 200),
11138
11227
  offset: clampedQueryInt(0, 0, Number.MAX_SAFE_INTEGER),
11139
11228
  // The feed's server-side filters (issues #292, #316): attribution and
@@ -11143,8 +11232,8 @@ var feedQuerySchema = z8.object({
11143
11232
  upstream: optionalQueryString,
11144
11233
  session_source: optionalQueryString
11145
11234
  });
11146
- var auditExportQuerySchema = z8.object({
11147
- format: z8.preprocess((value) => value === "csv" ? "csv" : "json", z8.enum(["json", "csv"])),
11235
+ var auditExportQuerySchema = z9.object({
11236
+ format: z9.preprocess((value) => value === "csv" ? "csv" : "json", z9.enum(["json", "csv"])),
11148
11237
  limit: clampedQueryInt(EXPORT_MAX_RECORDS, 1, EXPORT_MAX_RECORDS),
11149
11238
  tool: optionalQueryString,
11150
11239
  decision: optionalQueryString,
@@ -11164,7 +11253,7 @@ var auditExportQuerySchema = z8.object({
11164
11253
  upstream: optionalQueryString,
11165
11254
  session_source: optionalQueryString
11166
11255
  });
11167
- var auditQuerySchema = z8.object({
11256
+ var auditQuerySchema = z9.object({
11168
11257
  limit: clampedQueryInt(50, 1, LIST_MAX_PAGE_SIZE),
11169
11258
  offset: clampedQueryInt(0, 0, Number.MAX_SAFE_INTEGER),
11170
11259
  tool: optionalQueryString,
@@ -11186,21 +11275,21 @@ var auditQuerySchema = z8.object({
11186
11275
  upstream: optionalQueryString,
11187
11276
  session_source: optionalQueryString
11188
11277
  });
11189
- var budgetEventsQuerySchema = z8.object({
11278
+ var budgetEventsQuerySchema = z9.object({
11190
11279
  limit: clampedQueryInt(50, 1, LIST_MAX_PAGE_SIZE),
11191
11280
  offset: clampedQueryInt(0, 0, Number.MAX_SAFE_INTEGER)
11192
11281
  });
11193
- var budgetEventsExportQuerySchema = z8.object({
11194
- format: z8.preprocess((value) => value === "csv" ? "csv" : "json", z8.enum(["json", "csv"])),
11282
+ var budgetEventsExportQuerySchema = z9.object({
11283
+ format: z9.preprocess((value) => value === "csv" ? "csv" : "json", z9.enum(["json", "csv"])),
11195
11284
  limit: clampedQueryInt(EXPORT_MAX_RECORDS, 1, EXPORT_MAX_RECORDS)
11196
11285
  });
11197
- var analyticsQuerySchema = z8.object({
11286
+ var analyticsQuerySchema = z9.object({
11198
11287
  from: optionalQueryString,
11199
11288
  to: optionalQueryString,
11200
11289
  upstream: optionalQueryString
11201
11290
  });
11202
- var authSessionBodySchema = z8.object({
11203
- secret: z8.string()
11291
+ var authSessionBodySchema = z9.object({
11292
+ secret: z9.string()
11204
11293
  });
11205
11294
  var SESSION_COOKIE = "helio_session";
11206
11295
  var SESSION_TTL_MS = 8 * 60 * 60 * 1e3;
@@ -11685,7 +11774,8 @@ var EVENT_TYPES = [
11685
11774
  "limit_warning",
11686
11775
  "approval_notification_failed",
11687
11776
  "budget_update",
11688
- "budget_breached"
11777
+ "budget_breached",
11778
+ "policy_reload"
11689
11779
  ];
11690
11780
  var DashboardEventBus = class {
11691
11781
  emitter = new EventEmitter();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethelio/proxy",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "description": "Open-source MCP governance proxy for AI agents",
6
6
  "license": "Apache-2.0",