@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/README.md +7 -1
- package/dist/cli.js +774 -152
- package/dist/dashboard-assets/assets/{index-BRvkMXWl.js → index-D9zeFAzU.js} +2 -2
- package/dist/dashboard-assets/index.html +1 -1
- package/dist/index.d.ts +75 -9
- package/dist/index.js +167 -77
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
7390
|
-
//
|
|
7391
|
-
//
|
|
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
|
-
|
|
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.
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
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
|
|
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
|
-
|
|
7550
|
-
console.error(
|
|
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 ${
|
|
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` :
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
10708
|
-
type:
|
|
10709
|
-
user:
|
|
10710
|
-
actions:
|
|
10711
|
-
channel:
|
|
10712
|
-
message:
|
|
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
|
|
10843
|
-
var approveBody =
|
|
10844
|
-
approved_by:
|
|
10931
|
+
import { z as z8 } from "zod";
|
|
10932
|
+
var approveBody = z8.object({
|
|
10933
|
+
approved_by: z8.string().min(1)
|
|
10845
10934
|
});
|
|
10846
|
-
var denyBody =
|
|
10847
|
-
denied_by:
|
|
10848
|
-
reason:
|
|
10935
|
+
var denyBody = z8.object({
|
|
10936
|
+
denied_by: z8.string().min(1),
|
|
10937
|
+
reason: z8.string().optional()
|
|
10849
10938
|
});
|
|
10850
|
-
var breakGlassBody =
|
|
10851
|
-
approved_by:
|
|
10852
|
-
reason:
|
|
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 =
|
|
10866
|
-
status:
|
|
10954
|
+
var listApprovalsQuery = z8.object({
|
|
10955
|
+
status: z8.preprocess(
|
|
10867
10956
|
(value) => typeof value === "string" && approvalStatusSet.has(value) ? value : void 0,
|
|
10868
|
-
|
|
10957
|
+
z8.enum(APPROVAL_STATUSES).optional()
|
|
10869
10958
|
),
|
|
10870
|
-
limit:
|
|
10959
|
+
limit: z8.preprocess(
|
|
10871
10960
|
(value) => clampInt(typeof value === "string" ? value : void 0, 50, 1, 1e3),
|
|
10872
|
-
|
|
10961
|
+
z8.number().int()
|
|
10873
10962
|
),
|
|
10874
|
-
offset:
|
|
10963
|
+
offset: z8.preprocess(
|
|
10875
10964
|
(value) => clampInt(typeof value === "string" ? value : void 0, 0, 0, Number.MAX_SAFE_INTEGER),
|
|
10876
|
-
|
|
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
|
|
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 =
|
|
11208
|
+
var optionalQueryString = z9.preprocess(
|
|
11120
11209
|
(value) => typeof value === "string" && value.length > 0 ? value : void 0,
|
|
11121
|
-
|
|
11210
|
+
z9.string().optional()
|
|
11122
11211
|
);
|
|
11123
|
-
var optionalQueryInt =
|
|
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
|
-
},
|
|
11128
|
-
var queryBoolean =
|
|
11216
|
+
}, z9.number().int().optional());
|
|
11217
|
+
var queryBoolean = z9.preprocess(
|
|
11129
11218
|
(value) => value === "true" ? true : value === "false" ? false : void 0,
|
|
11130
|
-
|
|
11219
|
+
z9.boolean().optional()
|
|
11131
11220
|
);
|
|
11132
|
-
var clampedQueryInt = (fallback, min, max) =>
|
|
11221
|
+
var clampedQueryInt = (fallback, min, max) => z9.preprocess(
|
|
11133
11222
|
(value) => clampInt(typeof value === "string" ? value : void 0, fallback, min, max),
|
|
11134
|
-
|
|
11223
|
+
z9.number().int()
|
|
11135
11224
|
);
|
|
11136
|
-
var feedQuerySchema =
|
|
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 =
|
|
11147
|
-
format:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11194
|
-
format:
|
|
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 =
|
|
11286
|
+
var analyticsQuerySchema = z9.object({
|
|
11198
11287
|
from: optionalQueryString,
|
|
11199
11288
|
to: optionalQueryString,
|
|
11200
11289
|
upstream: optionalQueryString
|
|
11201
11290
|
});
|
|
11202
|
-
var authSessionBodySchema =
|
|
11203
|
-
secret:
|
|
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();
|