mergen-server 1.4.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/LICENSE +57 -0
- package/README.md +96 -0
- package/dist/__stubs__/calibration.js +471 -0
- package/dist/__stubs__/causal.js +601 -0
- package/dist/__stubs__/closed-source.js +150 -0
- package/dist/__stubs__/license.js +35 -0
- package/dist/__stubs__/plans.js +197 -0
- package/dist/app.js +467 -0
- package/dist/cli.js +250 -0
- package/dist/commands/agent-identity.js +79 -0
- package/dist/commands/gate.js +107 -0
- package/dist/commands/github.js +632 -0
- package/dist/commands/incident.js +644 -0
- package/dist/commands/policy.js +710 -0
- package/dist/commands/scan.js +243 -0
- package/dist/commands/setup.js +1218 -0
- package/dist/commands/shared.js +109 -0
- package/dist/commands/team.js +809 -0
- package/dist/datadog/blame-attribution.js +228 -0
- package/dist/datadog/client.js +159 -0
- package/dist/datadog/compactor.js +169 -0
- package/dist/datadog/fingerprinter.js +34 -0
- package/dist/datadog/incident-state.js +21 -0
- package/dist/datadog/line-matcher.js +56 -0
- package/dist/datadog/memory-store.js +399 -0
- package/dist/datadog/otel-trace.js +67 -0
- package/dist/index.js +505 -0
- package/dist/intelligence/action-risk.js +132 -0
- package/dist/intelligence/activity-feed.js +40 -0
- package/dist/intelligence/agent-identity.js +115 -0
- package/dist/intelligence/agent-pipeline.js +329 -0
- package/dist/intelligence/agent-profiles.js +81 -0
- package/dist/intelligence/ai-commit.js +16 -0
- package/dist/intelligence/approval-analytics.js +42 -0
- package/dist/intelligence/approval-events.js +5 -0
- package/dist/intelligence/arch-boundaries.js +94 -0
- package/dist/intelligence/arch-graph.js +117 -0
- package/dist/intelligence/autonomy.js +397 -0
- package/dist/intelligence/azure-broker.js +57 -0
- package/dist/intelligence/azure-proxy-sessions.js +49 -0
- package/dist/intelligence/baseline.js +1 -0
- package/dist/intelligence/behavior-baseline.js +134 -0
- package/dist/intelligence/billing.js +1 -0
- package/dist/intelligence/blast-radius.js +224 -0
- package/dist/intelligence/bypass.js +157 -0
- package/dist/intelligence/calibration-classifier.js +77 -0
- package/dist/intelligence/calibration-git-sync.js +74 -0
- package/dist/intelligence/calibration.js +1 -0
- package/dist/intelligence/cascade-detector.js +58 -0
- package/dist/intelligence/case-study-generator.js +126 -0
- package/dist/intelligence/causal-graph.js +126 -0
- package/dist/intelligence/causal.js +1 -0
- package/dist/intelligence/change-risk.js +102 -0
- package/dist/intelligence/compliance-report.js +134 -0
- package/dist/intelligence/confidence-report.js +85 -0
- package/dist/intelligence/corpus-to-policy.js +140 -0
- package/dist/intelligence/credential-broker.js +112 -0
- package/dist/intelligence/credential-misuse.js +39 -0
- package/dist/intelligence/debug-sessions.js +108 -0
- package/dist/intelligence/decision-escalation.js +36 -0
- package/dist/intelligence/decision-memory.js +47 -0
- package/dist/intelligence/decision-similarity.js +85 -0
- package/dist/intelligence/default-safety-tests.js +53 -0
- package/dist/intelligence/degradation-watcher.js +108 -0
- package/dist/intelligence/detector-plugins.js +70 -0
- package/dist/intelligence/detectors.js +1 -0
- package/dist/intelligence/device-auth.js +138 -0
- package/dist/intelligence/diff-size.js +42 -0
- package/dist/intelligence/ebpf-verify.js +45 -0
- package/dist/intelligence/enterprise-policy-engine.js +1122 -0
- package/dist/intelligence/error-fingerprint.js +1 -0
- package/dist/intelligence/execution-gate.js +100 -0
- package/dist/intelligence/execution-mode.js +22 -0
- package/dist/intelligence/gate-analytics.js +256 -0
- package/dist/intelligence/gate-decision.js +64 -0
- package/dist/intelligence/gcp-broker.js +50 -0
- package/dist/intelligence/git-adr-sync.js +207 -0
- package/dist/intelligence/hitl-hold.js +460 -0
- package/dist/intelligence/hypothesis-history.js +93 -0
- package/dist/intelligence/idp-identity.js +83 -0
- package/dist/intelligence/impl-critic.js +216 -0
- package/dist/intelligence/incident-autopilot.js +622 -0
- package/dist/intelligence/incident-replay.js +162 -0
- package/dist/intelligence/incident-result-cache.js +33 -0
- package/dist/intelligence/incident-similarity.js +58 -0
- package/dist/intelligence/infra-detectors.js +312 -0
- package/dist/intelligence/license.js +185 -0
- package/dist/intelligence/llm-spokesperson.js +62 -0
- package/dist/intelligence/mcp-prompts.js +320 -0
- package/dist/intelligence/mcp-resources.js +171 -0
- package/dist/intelligence/normalize.js +108 -0
- package/dist/intelligence/notifications.js +83 -0
- package/dist/intelligence/oidc-issuer.js +87 -0
- package/dist/intelligence/override-corpus.js +597 -0
- package/dist/intelligence/planning-gate.js +84 -0
- package/dist/intelligence/plans.js +223 -0
- package/dist/intelligence/platt-scaling.js +164 -0
- package/dist/intelligence/policy-proposals.js +84 -0
- package/dist/intelligence/policy-suggester.js +161 -0
- package/dist/intelligence/policy-sync.js +103 -0
- package/dist/intelligence/policy-test-runner.js +35 -0
- package/dist/intelligence/postmortem-parser.js +181 -0
- package/dist/intelligence/postmortem-retrieval.js +224 -0
- package/dist/intelligence/postmortem-store.js +596 -0
- package/dist/intelligence/pr-commenter.js +81 -0
- package/dist/intelligence/pr-policy-gate.js +38 -0
- package/dist/intelligence/pr-shadow-analyzer.js +229 -0
- package/dist/intelligence/process-tree.js +108 -0
- package/dist/intelligence/prompts.js +1 -0
- package/dist/intelligence/replay.js +38 -0
- package/dist/intelligence/repro-steps.js +1 -0
- package/dist/intelligence/resource-extractors.js +0 -0
- package/dist/intelligence/resource-file-context.js +138 -0
- package/dist/intelligence/risk-score.js +22 -0
- package/dist/intelligence/rollback.js +137 -0
- package/dist/intelligence/runbook-updater.js +143 -0
- package/dist/intelligence/sandbox.js +194 -0
- package/dist/intelligence/script-trust.js +104 -0
- package/dist/intelligence/session-metrics.js +67 -0
- package/dist/intelligence/session-threat-tracker.js +231 -0
- package/dist/intelligence/shadow-digest-cron.js +81 -0
- package/dist/intelligence/shadow-log.js +284 -0
- package/dist/intelligence/shell-ast.js +145 -0
- package/dist/intelligence/siem-forward.js +104 -0
- package/dist/intelligence/slack-digest.js +151 -0
- package/dist/intelligence/slack-override-loop.js +217 -0
- package/dist/intelligence/slack-routing.js +67 -0
- package/dist/intelligence/slack.js +900 -0
- package/dist/intelligence/sql-ast.js +31 -0
- package/dist/intelligence/team.js +1 -0
- package/dist/intelligence/telemetry.js +76 -0
- package/dist/intelligence/threshold-optimizer.js +75 -0
- package/dist/intelligence/token-budget.js +66 -0
- package/dist/intelligence/tool-guard.js +708 -0
- package/dist/intelligence/tool-manifest 2.js +74 -0
- package/dist/intelligence/tool-manifest.js +125 -0
- package/dist/intelligence/tools-analysis.js +857 -0
- package/dist/intelligence/tools-arch.js +152 -0
- package/dist/intelligence/tools-autonomy.js +495 -0
- package/dist/intelligence/tools-blast-radius.js +140 -0
- package/dist/intelligence/tools-browser.js +431 -0
- package/dist/intelligence/tools-change-timeline.js +154 -0
- package/dist/intelligence/tools-credentials.js +117 -0
- package/dist/intelligence/tools-datadog.js +233 -0
- package/dist/intelligence/tools-debug-sessions.js +209 -0
- package/dist/intelligence/tools-discovery.js +284 -0
- package/dist/intelligence/tools-infra.js +612 -0
- package/dist/intelligence/tools-intent.js +128 -0
- package/dist/intelligence/tools-memory.js +514 -0
- package/dist/intelligence/tools-runbook.js +951 -0
- package/dist/intelligence/tools-sessions.js +107 -0
- package/dist/intelligence/tools-state.js +232 -0
- package/dist/intelligence/tools-utility.js +430 -0
- package/dist/intelligence/tools-validate.js +215 -0
- package/dist/intelligence/tools.js +1 -0
- package/dist/intelligence/trace-context.js +11 -0
- package/dist/intelligence/unclassified-clusters.js +87 -0
- package/dist/intelligence/usage.js +195 -0
- package/dist/routes/active-authors.js +63 -0
- package/dist/routes/activity-feed.js +21 -0
- package/dist/routes/adr.js +117 -0
- package/dist/routes/agent-activity.js +184 -0
- package/dist/routes/agent-blunders.js +163 -0
- package/dist/routes/agents.js +175 -0
- package/dist/routes/api-keys.js +65 -0
- package/dist/routes/arch.js +75 -0
- package/dist/routes/audit-export.js +93 -0
- package/dist/routes/billing-dashboard.js +158 -0
- package/dist/routes/billing-outcome.js +77 -0
- package/dist/routes/calibration.js +305 -0
- package/dist/routes/ci-gate-history.js +20 -0
- package/dist/routes/ci-gate.js +232 -0
- package/dist/routes/ci.js +293 -0
- package/dist/routes/compliance-report.js +24 -0
- package/dist/routes/confidence.js +30 -0
- package/dist/routes/credentials.js +119 -0
- package/dist/routes/dashboard.js +1407 -0
- package/dist/routes/decision-memory.js +42 -0
- package/dist/routes/demo.js +844 -0
- package/dist/routes/device-auth-stub.js +173 -0
- package/dist/routes/explain-why.js +39 -0
- package/dist/routes/gate-analytics.js +213 -0
- package/dist/routes/gate.js +28 -0
- package/dist/routes/github-webhook.js +364 -0
- package/dist/routes/habituation.js +30 -0
- package/dist/routes/health-integrations.js +243 -0
- package/dist/routes/heartbeats.js +45 -0
- package/dist/routes/hitl.js +364 -0
- package/dist/routes/impact-report.js +645 -0
- package/dist/routes/incident-webhook.js +63 -0
- package/dist/routes/incidents.js +366 -0
- package/dist/routes/layers.js +62 -0
- package/dist/routes/license.js +187 -0
- package/dist/routes/oidc-issuer.js +30 -0
- package/dist/routes/onboarding.js +170 -0
- package/dist/routes/otel.js +67 -0
- package/dist/routes/otlp-receiver.js +179 -0
- package/dist/routes/overrides.js +241 -0
- package/dist/routes/pagerduty.js +258 -0
- package/dist/routes/policies.js +809 -0
- package/dist/routes/policy-nl.js +169 -0
- package/dist/routes/postmortem.js +102 -0
- package/dist/routes/pr-shadow.js +47 -0
- package/dist/routes/rbac.js +61 -0
- package/dist/routes/replay.js +17 -0
- package/dist/routes/risk-report.js +164 -0
- package/dist/routes/runbooks.js +125 -0
- package/dist/routes/safety-test.js +174 -0
- package/dist/routes/sdk.js +222 -0
- package/dist/routes/sensor.js +819 -0
- package/dist/routes/sentry.js +140 -0
- package/dist/routes/sessions.js +43 -0
- package/dist/routes/setup-ui.js +594 -0
- package/dist/routes/shadow-report.js +107 -0
- package/dist/routes/slack-routing.js +171 -0
- package/dist/routes/team-usage.js +71 -0
- package/dist/routes/telemetry.js +28 -0
- package/dist/routes/tenants.js +199 -0
- package/dist/routes/tickets.js +167 -0
- package/dist/routes/validate.js +13 -0
- package/dist/routes/war-room.js +113 -0
- package/dist/scripts/check-arch.js +22 -0
- package/dist/seeds/community-corpus.js +176 -0
- package/dist/seeds/corpus.js +1224 -0
- package/dist/sensor/action-ledger.js +327 -0
- package/dist/sensor/adr-store.js +140 -0
- package/dist/sensor/agent-blunder-store.js +370 -0
- package/dist/sensor/agent-context-store.js +225 -0
- package/dist/sensor/agent-memory-store.js +231 -0
- package/dist/sensor/audit-fetch.js +23 -0
- package/dist/sensor/audit-log.js +273 -0
- package/dist/sensor/buffer-schemas.js +225 -0
- package/dist/sensor/buffer.js +599 -0
- package/dist/sensor/bypass-tracker.js +93 -0
- package/dist/sensor/ci-gate-history.js +70 -0
- package/dist/sensor/cloud-auth.js +183 -0
- package/dist/sensor/commit-context-store.js +235 -0
- package/dist/sensor/docker-log-stream.js +164 -0
- package/dist/sensor/docker-monitor.js +122 -0
- package/dist/sensor/extended-buffer.js +46 -0
- package/dist/sensor/feedback-token.js +41 -0
- package/dist/sensor/file-lock.js +6 -0
- package/dist/sensor/fs-watcher.js +106 -0
- package/dist/sensor/gate-heartbeat.js +122 -0
- package/dist/sensor/git-suspect.js +136 -0
- package/dist/sensor/habituation-store.js +77 -0
- package/dist/sensor/heartbeat-monitor.js +133 -0
- package/dist/sensor/incident-store.js +340 -0
- package/dist/sensor/infra-normalizer.js +513 -0
- package/dist/sensor/ingest.js +339 -0
- package/dist/sensor/jest-reporter.js +52 -0
- package/dist/sensor/k8s-events.js +132 -0
- package/dist/sensor/layer2-store.js +111 -0
- package/dist/sensor/layer3-store.js +230 -0
- package/dist/sensor/layer4-store.js +147 -0
- package/dist/sensor/logger.js +13 -0
- package/dist/sensor/otel-exporter.js +161 -0
- package/dist/sensor/paths.js +71 -0
- package/dist/sensor/policy-history.js +147 -0
- package/dist/sensor/pr-shadow-store.js +83 -0
- package/dist/sensor/process-watcher.js +162 -0
- package/dist/sensor/rbac.js +92 -0
- package/dist/sensor/redact.js +114 -0
- package/dist/sensor/redis-store.js +191 -0
- package/dist/sensor/route-reachability.js +64 -0
- package/dist/sensor/security-utils.js +18 -0
- package/dist/sensor/service-graph.js +170 -0
- package/dist/sensor/service-topology.js +227 -0
- package/dist/sensor/session-history.js +74 -0
- package/dist/sensor/session-persist.js +38 -0
- package/dist/sensor/shadow-promote.js +122 -0
- package/dist/sensor/sourcemap.js +281 -0
- package/dist/sensor/sqlite-store.js +205 -0
- package/dist/sensor/sso.js +164 -0
- package/dist/sensor/vitest-reporter.js +65 -0
- package/dist/sensor/watcher.js +48 -0
- package/dist/storage/interfaces.js +0 -0
- package/dist/storage/pg/pg-action-ledger.js +138 -0
- package/dist/storage/pg/pg-approval-store.js +107 -0
- package/dist/storage/pg/pg-blunder-store.js +193 -0
- package/dist/storage/pg/pg-ci-gate-history.js +83 -0
- package/dist/storage/pg/pg-client.js +24 -0
- package/dist/storage/pg/pg-event-store.js +63 -0
- package/dist/storage/pg/pg-incident-store.js +190 -0
- package/dist/storage/pg/pg-migrations.js +26 -0
- package/dist/storage/pg/pg-override-corpus.js +447 -0
- package/dist/storage/pg/pg-shadow-log.js +129 -0
- package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
- package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
- package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
- package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
- package/dist/storage/sqlite/sqlite-event-store.js +29 -0
- package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
- package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
- package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
- package/dist/storage/store-factory.js +55 -0
- package/dist/storage/store-registry.js +31 -0
- package/dist/update-checker.js +107 -0
- package/dist/workers/autopilot-worker.js +28 -0
- package/dist/workers/notification-worker.js +28 -0
- package/dist/workers/queues.js +58 -0
- package/dist/workers/validation-worker.js +24 -0
- package/dist/workers/worker-registry.js +18 -0
- package/package.json +123 -0
- package/sdk/mergen-inject.js +260 -0
- package/sdk/node.js +313 -0
- package/sdk/vite-plugin.ts +57 -0
- package/sdk/webpack-plugin.js +106 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../__stubs__/closed-source.js';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { approvalEvents } from "./approval-events.js";
|
|
3
|
+
import { APPROVALS_FILE, DATA_DIR, zeroRetentionMode } from "../sensor/paths.js";
|
|
4
|
+
import logger from "../sensor/logger.js";
|
|
5
|
+
const APPROVAL_WINDOW_MS = 15 * 60 * 1e3;
|
|
6
|
+
const _pending = /* @__PURE__ */ new Map();
|
|
7
|
+
let _loaded = false;
|
|
8
|
+
let _persistTimer = null;
|
|
9
|
+
function schedulePersist() {
|
|
10
|
+
if (zeroRetentionMode()) return;
|
|
11
|
+
if (_persistTimer) clearTimeout(_persistTimer);
|
|
12
|
+
_persistTimer = setTimeout(() => {
|
|
13
|
+
_persistTimer = null;
|
|
14
|
+
_persist();
|
|
15
|
+
}, 500);
|
|
16
|
+
}
|
|
17
|
+
function _persist() {
|
|
18
|
+
try {
|
|
19
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
20
|
+
const payload = { version: 1, pending: [..._pending.entries()] };
|
|
21
|
+
const tmp = `${APPROVALS_FILE}.tmp.${process.pid}`;
|
|
22
|
+
fs.writeFileSync(tmp, JSON.stringify(payload), "utf8");
|
|
23
|
+
fs.renameSync(tmp, APPROVALS_FILE);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
logger.warn({ err }, "execution-gate: persist failed");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function flushApprovals() {
|
|
29
|
+
_persist();
|
|
30
|
+
}
|
|
31
|
+
function load() {
|
|
32
|
+
if (_loaded) return;
|
|
33
|
+
_loaded = true;
|
|
34
|
+
if (zeroRetentionMode() || !fs.existsSync(APPROVALS_FILE)) return;
|
|
35
|
+
try {
|
|
36
|
+
const raw = JSON.parse(fs.readFileSync(APPROVALS_FILE, "utf8"));
|
|
37
|
+
if (raw?.version !== 1 || !Array.isArray(raw.pending)) return;
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
let restored = 0;
|
|
40
|
+
for (const [pid, record] of raw.pending) {
|
|
41
|
+
if (record.expiresAt <= now) continue;
|
|
42
|
+
_pending.set(pid, record);
|
|
43
|
+
restored++;
|
|
44
|
+
}
|
|
45
|
+
if (restored > 0) {
|
|
46
|
+
logger.info({ restored }, "execution-gate: restored pending approvals from disk");
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.warn({ err }, "execution-gate: failed to load persisted approvals \u2014 starting fresh");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function requestApproval(opts) {
|
|
53
|
+
load();
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
_pending.set(opts.pid, { ...opts, requestedAt: now, expiresAt: now + APPROVAL_WINDOW_MS });
|
|
56
|
+
schedulePersist();
|
|
57
|
+
logger.info({ pid: opts.pid, tier: opts.tier, command: opts.command }, "execution-gate: awaiting Slack approval");
|
|
58
|
+
}
|
|
59
|
+
function approveExecution(pid) {
|
|
60
|
+
load();
|
|
61
|
+
const record = _pending.get(pid);
|
|
62
|
+
if (!record) return null;
|
|
63
|
+
_pending.delete(pid);
|
|
64
|
+
schedulePersist();
|
|
65
|
+
return record;
|
|
66
|
+
}
|
|
67
|
+
function denyExecution(pid) {
|
|
68
|
+
load();
|
|
69
|
+
if (!_pending.has(pid)) return false;
|
|
70
|
+
_pending.delete(pid);
|
|
71
|
+
schedulePersist();
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
function pruneExpired() {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
for (const [pid, record] of _pending) {
|
|
77
|
+
if (now <= record.expiresAt) continue;
|
|
78
|
+
_pending.delete(pid);
|
|
79
|
+
approvalEvents.emit("approval:expired", pid, "\u23F0 _Approval window expired (15 min). Re-trigger autopilot or apply fix manually._");
|
|
80
|
+
logger.info({ pid, command: record.command }, "execution-gate: approval window expired");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const _expiryHandle = setInterval(pruneExpired, 6e4);
|
|
84
|
+
_expiryHandle.unref();
|
|
85
|
+
function _resetForTesting() {
|
|
86
|
+
_pending.clear();
|
|
87
|
+
_loaded = true;
|
|
88
|
+
if (_persistTimer) {
|
|
89
|
+
clearTimeout(_persistTimer);
|
|
90
|
+
_persistTimer = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
_resetForTesting,
|
|
95
|
+
approveExecution,
|
|
96
|
+
denyExecution,
|
|
97
|
+
flushApprovals,
|
|
98
|
+
pruneExpired,
|
|
99
|
+
requestApproval
|
|
100
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function isAutopilotEnabled() {
|
|
2
|
+
return process.env.MERGEN_AUTOPILOT === "true";
|
|
3
|
+
}
|
|
4
|
+
function isShadowMode() {
|
|
5
|
+
const shadowEnv = process.env.MERGEN_SHADOW_MODE;
|
|
6
|
+
const autopilot = isAutopilotEnabled();
|
|
7
|
+
if (shadowEnv === "true") return true;
|
|
8
|
+
if (autopilot) return shadowEnv !== "false";
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
function isLiveExecutionEnabled() {
|
|
12
|
+
return isAutopilotEnabled() && !isShadowMode();
|
|
13
|
+
}
|
|
14
|
+
function isPolicyDryRun() {
|
|
15
|
+
return process.env.MERGEN_POLICY_DRY_RUN === "true";
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
isAutopilotEnabled,
|
|
19
|
+
isLiveExecutionEnabled,
|
|
20
|
+
isPolicyDryRun,
|
|
21
|
+
isShadowMode
|
|
22
|
+
};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { resolveAgentIdentity } from "./agent-identity.js";
|
|
2
|
+
const _retryStats = /* @__PURE__ */ new Map();
|
|
3
|
+
const _pendingRetries = [];
|
|
4
|
+
const RETRY_WINDOW_MS = 6e4;
|
|
5
|
+
function _mostRecentRetryIdx(now) {
|
|
6
|
+
for (let i = _pendingRetries.length - 1; i >= 0; i--) {
|
|
7
|
+
if (now - _pendingRetries[i].firedAt <= RETRY_WINDOW_MS) return i;
|
|
8
|
+
}
|
|
9
|
+
return -1;
|
|
10
|
+
}
|
|
11
|
+
function _ensureRetryStats(ruleId) {
|
|
12
|
+
if (!_retryStats.has(ruleId)) _retryStats.set(ruleId, { fired: 0, retryPassed: 0, retryBlocked: 0 });
|
|
13
|
+
return _retryStats.get(ruleId);
|
|
14
|
+
}
|
|
15
|
+
let _totalBlockedCount = 0;
|
|
16
|
+
function getBlockedActionCount() {
|
|
17
|
+
return _totalBlockedCount;
|
|
18
|
+
}
|
|
19
|
+
function recordGateBlock(triggeredRules) {
|
|
20
|
+
_totalBlockedCount++;
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const idx = _mostRecentRetryIdx(now);
|
|
23
|
+
if (idx !== -1) {
|
|
24
|
+
const pending = _pendingRetries.splice(idx, 1)[0];
|
|
25
|
+
for (const ruleId of pending.triggeredRules) {
|
|
26
|
+
_ensureRetryStats(ruleId).retryBlocked++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const ruleId of triggeredRules) {
|
|
30
|
+
_ensureRetryStats(ruleId).fired++;
|
|
31
|
+
}
|
|
32
|
+
_pendingRetries.push({ triggeredRules, firedAt: now });
|
|
33
|
+
_schedulePersist();
|
|
34
|
+
}
|
|
35
|
+
function recordGatePass() {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const idx = _mostRecentRetryIdx(now);
|
|
38
|
+
if (idx === -1) return;
|
|
39
|
+
const pending = _pendingRetries.splice(idx, 1)[0];
|
|
40
|
+
for (const ruleId of pending.triggeredRules) {
|
|
41
|
+
_ensureRetryStats(ruleId).retryPassed++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function getRetryStats() {
|
|
45
|
+
return _retryStats;
|
|
46
|
+
}
|
|
47
|
+
const _toolCallCounts = /* @__PURE__ */ new Map();
|
|
48
|
+
const _unguardedCounts = /* @__PURE__ */ new Map();
|
|
49
|
+
const _ruleFirings = /* @__PURE__ */ new Map();
|
|
50
|
+
function recordGateCoverage(toolName, triggeredRules) {
|
|
51
|
+
_toolCallCounts.set(toolName, (_toolCallCounts.get(toolName) ?? 0) + 1);
|
|
52
|
+
if (triggeredRules.length === 0) {
|
|
53
|
+
_unguardedCounts.set(toolName, (_unguardedCounts.get(toolName) ?? 0) + 1);
|
|
54
|
+
}
|
|
55
|
+
for (const ruleId of triggeredRules) {
|
|
56
|
+
_ruleFirings.set(ruleId, (_ruleFirings.get(ruleId) ?? 0) + 1);
|
|
57
|
+
}
|
|
58
|
+
_schedulePersist();
|
|
59
|
+
}
|
|
60
|
+
function getToolCallCounts() {
|
|
61
|
+
return _toolCallCounts;
|
|
62
|
+
}
|
|
63
|
+
function getUnguardedCounts() {
|
|
64
|
+
return _unguardedCounts;
|
|
65
|
+
}
|
|
66
|
+
function getRuleFirings() {
|
|
67
|
+
return _ruleFirings;
|
|
68
|
+
}
|
|
69
|
+
const _hitlStats = /* @__PURE__ */ new Map();
|
|
70
|
+
function _ensureHitlStats(ruleId) {
|
|
71
|
+
if (!_hitlStats.has(ruleId)) {
|
|
72
|
+
_hitlStats.set(ruleId, { approvals: 0, denials: 0, totalApprovalLatencyMs: 0, totalDenialLatencyMs: 0 });
|
|
73
|
+
}
|
|
74
|
+
return _hitlStats.get(ruleId);
|
|
75
|
+
}
|
|
76
|
+
function recordHitlDecision(triggeredRules, decision, heldAt) {
|
|
77
|
+
const latencyMs = Date.now() - heldAt;
|
|
78
|
+
for (const ruleId of triggeredRules) {
|
|
79
|
+
const stats = _ensureHitlStats(ruleId);
|
|
80
|
+
if (decision === "approve") {
|
|
81
|
+
stats.approvals++;
|
|
82
|
+
stats.totalApprovalLatencyMs += latencyMs;
|
|
83
|
+
} else {
|
|
84
|
+
stats.denials++;
|
|
85
|
+
stats.totalDenialLatencyMs += latencyMs;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function getHitlStats() {
|
|
90
|
+
return _hitlStats;
|
|
91
|
+
}
|
|
92
|
+
const RING_CAP = 500;
|
|
93
|
+
const _gateRing = [];
|
|
94
|
+
const _sseSubscribers = /* @__PURE__ */ new Set();
|
|
95
|
+
function subscribeGateStream(cb) {
|
|
96
|
+
_sseSubscribers.add(cb);
|
|
97
|
+
return () => _sseSubscribers.delete(cb);
|
|
98
|
+
}
|
|
99
|
+
function recordGateEvent(event) {
|
|
100
|
+
_gateRing.push(event);
|
|
101
|
+
if (_gateRing.length > RING_CAP) _gateRing.shift();
|
|
102
|
+
for (const sub of _sseSubscribers) {
|
|
103
|
+
try {
|
|
104
|
+
sub(event);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (event.agentId) {
|
|
109
|
+
const events = getGateEvents();
|
|
110
|
+
void import("./behavior-baseline.js").then(({ onGateEvent }) => onGateEvent(event.agentId, events));
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const identity = resolveAgentIdentity();
|
|
114
|
+
if (identity.verified && identity.userId) event.userId = identity.userId;
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
void _appendToLedger(event);
|
|
118
|
+
_schedulePersist();
|
|
119
|
+
}
|
|
120
|
+
async function _appendToLedger(event) {
|
|
121
|
+
try {
|
|
122
|
+
const { getStores } = await import("../storage/store-registry.js");
|
|
123
|
+
const resource = event.resource !== void 0 ? event.resource : await _deriveResource(event.command);
|
|
124
|
+
await getStores().actionLedger.append({
|
|
125
|
+
ts: event.ts,
|
|
126
|
+
agentId: event.agentId,
|
|
127
|
+
userId: event.userId ?? process.env.MERGEN_USER_ID ?? null,
|
|
128
|
+
tool: event.toolName,
|
|
129
|
+
command: event.command,
|
|
130
|
+
resource,
|
|
131
|
+
verdict: event.verdict,
|
|
132
|
+
policyMatched: event.triggeredRules,
|
|
133
|
+
reason: event.reason ?? event.guidedAlternative ?? null,
|
|
134
|
+
credentialIssued: event.credentialIssued ?? false,
|
|
135
|
+
credentialScope: event.credentialScope ?? null,
|
|
136
|
+
environment: event.environment ?? null,
|
|
137
|
+
traceId: event.traceId ?? null
|
|
138
|
+
});
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function _deriveResource(command) {
|
|
143
|
+
if (!command) return null;
|
|
144
|
+
try {
|
|
145
|
+
const [{ parseShellCommand }, { extractResourcesFromHaystack }] = await Promise.all([
|
|
146
|
+
import("./shell-ast.js"),
|
|
147
|
+
import("./resource-extractors.js")
|
|
148
|
+
]);
|
|
149
|
+
const parsed = parseShellCommand(command);
|
|
150
|
+
if (!parsed) return null;
|
|
151
|
+
return extractResourcesFromHaystack(parsed)[0] ?? null;
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function getGateEvents() {
|
|
157
|
+
return [..._gateRing];
|
|
158
|
+
}
|
|
159
|
+
function getReformulationRates() {
|
|
160
|
+
const result = /* @__PURE__ */ new Map();
|
|
161
|
+
for (const [ruleId, stats] of _retryStats) {
|
|
162
|
+
const reformulated = stats.retryPassed;
|
|
163
|
+
result.set(ruleId, {
|
|
164
|
+
fired: stats.fired,
|
|
165
|
+
reformulated,
|
|
166
|
+
rate: stats.fired > 0 ? reformulated / stats.fired : 0
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
const FATIGUE_WINDOW_MS = 60 * 60 * 1e3;
|
|
172
|
+
const FATIGUE_THRESHOLD = 5;
|
|
173
|
+
const _holdTimestamps = [];
|
|
174
|
+
function recordHitlHold() {
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
_holdTimestamps.push(now);
|
|
177
|
+
const cutoff = now - FATIGUE_WINDOW_MS;
|
|
178
|
+
while (_holdTimestamps.length > 0 && _holdTimestamps[0] < cutoff) _holdTimestamps.shift();
|
|
179
|
+
}
|
|
180
|
+
function getHitlFatigueStatus() {
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
const cutoff = now - FATIGUE_WINDOW_MS;
|
|
183
|
+
const holdsLastHour = _holdTimestamps.filter((t) => t >= cutoff).length;
|
|
184
|
+
const fatigued = holdsLastHour >= FATIGUE_THRESHOLD;
|
|
185
|
+
return {
|
|
186
|
+
holdsLastHour,
|
|
187
|
+
fatigued,
|
|
188
|
+
threshold: FATIGUE_THRESHOLD,
|
|
189
|
+
recommendation: fatigued ? `${holdsLastHour} HITL holds in the last hour \u2014 consider tightening the policy to auto-block these patterns rather than holding them for approval.` : null
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
let _persistTimer = null;
|
|
193
|
+
function persistGateAnalytics() {
|
|
194
|
+
import("../sensor/agent-context-store.js").then(({ agentContextStore }) => {
|
|
195
|
+
const snapshot = {
|
|
196
|
+
version: 1,
|
|
197
|
+
retryStats: [..._retryStats.entries()],
|
|
198
|
+
toolCallCounts: [..._toolCallCounts.entries()],
|
|
199
|
+
unguardedCounts: [..._unguardedCounts.entries()],
|
|
200
|
+
ruleFirings: [..._ruleFirings.entries()],
|
|
201
|
+
hitlStats: [..._hitlStats.entries()],
|
|
202
|
+
events: _gateRing.slice(-500)
|
|
203
|
+
};
|
|
204
|
+
agentContextStore.store("__gate", "gate_analytics_v1", JSON.stringify(snapshot), 0);
|
|
205
|
+
}).catch(() => {
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function _schedulePersist() {
|
|
209
|
+
if (_persistTimer) return;
|
|
210
|
+
_persistTimer = setTimeout(() => {
|
|
211
|
+
_persistTimer = null;
|
|
212
|
+
persistGateAnalytics();
|
|
213
|
+
}, 6e4);
|
|
214
|
+
if (_persistTimer && typeof _persistTimer === "object" && "unref" in _persistTimer) {
|
|
215
|
+
_persistTimer.unref();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function hydrateGateAnalytics() {
|
|
219
|
+
try {
|
|
220
|
+
const { agentContextStore } = await import("../sensor/agent-context-store.js");
|
|
221
|
+
const entries = agentContextStore.recall("__gate", "gate_analytics_v1", 1);
|
|
222
|
+
if (entries.length === 0) return;
|
|
223
|
+
const snap = JSON.parse(entries[0].value);
|
|
224
|
+
if (snap?.version !== 1) return;
|
|
225
|
+
for (const [k, v] of snap.retryStats ?? []) _retryStats.set(k, v);
|
|
226
|
+
for (const [k, v] of snap.toolCallCounts ?? []) _toolCallCounts.set(k, v);
|
|
227
|
+
for (const [k, v] of snap.unguardedCounts ?? []) _unguardedCounts.set(k, v);
|
|
228
|
+
for (const [k, v] of snap.ruleFirings ?? []) _ruleFirings.set(k, v);
|
|
229
|
+
for (const [k, v] of snap.hitlStats ?? []) _hitlStats.set(k, v);
|
|
230
|
+
if (Array.isArray(snap.events)) {
|
|
231
|
+
_gateRing.length = 0;
|
|
232
|
+
_gateRing.push(...snap.events.slice(-RING_CAP));
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
getBlockedActionCount,
|
|
239
|
+
getGateEvents,
|
|
240
|
+
getHitlFatigueStatus,
|
|
241
|
+
getHitlStats,
|
|
242
|
+
getReformulationRates,
|
|
243
|
+
getRetryStats,
|
|
244
|
+
getRuleFirings,
|
|
245
|
+
getToolCallCounts,
|
|
246
|
+
getUnguardedCounts,
|
|
247
|
+
hydrateGateAnalytics,
|
|
248
|
+
persistGateAnalytics,
|
|
249
|
+
recordGateBlock,
|
|
250
|
+
recordGateCoverage,
|
|
251
|
+
recordGateEvent,
|
|
252
|
+
recordGatePass,
|
|
253
|
+
recordHitlDecision,
|
|
254
|
+
recordHitlHold,
|
|
255
|
+
subscribeGateStream
|
|
256
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { normalizeForMatching } from "./normalize.js";
|
|
2
|
+
function extractAllStrings(obj, depth = 0) {
|
|
3
|
+
if (depth > 6) return [];
|
|
4
|
+
if (typeof obj === "string") return obj.length > 0 ? [obj] : [];
|
|
5
|
+
if (Array.isArray(obj)) return obj.flatMap((v) => extractAllStrings(v, depth + 1));
|
|
6
|
+
if (obj !== null && typeof obj === "object") {
|
|
7
|
+
return Object.values(obj).flatMap((v) => extractAllStrings(v, depth + 1));
|
|
8
|
+
}
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const INJECTION_PATTERNS = [
|
|
12
|
+
/ignore\s+(all\s+)?previous\s+instructions?/i,
|
|
13
|
+
/disregard\s+(the\s+)?(above|previous|prior)/i,
|
|
14
|
+
/forget\s+(all\s+)?previous\s+instructions?/i,
|
|
15
|
+
/you\s+are\s+now\s+(?:a|an)\s+/i,
|
|
16
|
+
/new\s+system\s+prompt/i,
|
|
17
|
+
/\bjailbreak\b/i,
|
|
18
|
+
/\bdan\s+mode\b/i,
|
|
19
|
+
/override\s+(all\s+)?(?:safety|security|policy)\s+(rules?|constraints?|restrictions?)/i,
|
|
20
|
+
// Additional paraphrase variants
|
|
21
|
+
/act\s+as\s+(?:if\s+)?(?:you\s+(?:have\s+no|are\s+without)\s+(?:restrictions?|guidelines?|rules?))/i,
|
|
22
|
+
/from\s+now\s+on\s+(?:you\s+(?:are|will|must|should))/i,
|
|
23
|
+
/pretend\s+(?:you\s+(?:have\s+no|are\s+without)|that\s+(?:safety|policy|rules?))/i,
|
|
24
|
+
/(?:ignore|bypass|circumvent|disable)\s+(?:your\s+)?(?:safety|security|policy|guidelines?|restrictions?)/i,
|
|
25
|
+
/(?:system|instruction|prompt)\s+(?:override|injection|hijack)/i,
|
|
26
|
+
/<\s*(?:system|instructions?)\s*>/i
|
|
27
|
+
];
|
|
28
|
+
function detectInjection(text) {
|
|
29
|
+
const normalized = normalizeForMatching(text);
|
|
30
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
31
|
+
if (pattern.test(normalized)) return pattern.source;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const COMMAND_ALTERNATIVES = [
|
|
36
|
+
[/terraform destroy/i, "Run `terraform plan -destroy` to preview the blast radius and share the plan output, then request human approval before proceeding."],
|
|
37
|
+
[/kubectl delete/i, "Run `kubectl describe <resource>` to confirm the target and current state, then request HITL approval before deletion."],
|
|
38
|
+
[/drop (table|database)/i, "Export a schema snapshot, confirm row counts, then create a reversible migration with a rollback path and request HITL approval."],
|
|
39
|
+
[/truncate table/i, "Confirm row count with `SELECT count(*) FROM <table>` and back up the data, then request approval to truncate."],
|
|
40
|
+
[/rm -rf/i, "List the target first with `ls -la <path>` to confirm scope, then request human approval before deleting."],
|
|
41
|
+
[/(destroy|nuke|wipe)\b/i, "Describe the specific resource and intended outcome, then request human approval \u2014 this action is irreversible."]
|
|
42
|
+
];
|
|
43
|
+
const RULE_ALTERNATIVES = {
|
|
44
|
+
policy_auth_batch_window: "Auth changes are locked during the Friday settlement window (12:00\u201324:00 UTC). Schedule this for after Saturday 00:00 UTC, or submit a change request via HITL for manual override.",
|
|
45
|
+
hold_schema_mutations: "Schema mutations require operator approval. Describe the migration intent, submit it for HITL review, and await the operator response before proceeding.",
|
|
46
|
+
policy_prod_database_warn: "Database migrations should run via automated pipelines. Open a PR to trigger the migration workflow rather than running it directly."
|
|
47
|
+
};
|
|
48
|
+
function getSuggestedAlternative(triggeredRules, commandArg) {
|
|
49
|
+
for (const ruleId of triggeredRules) {
|
|
50
|
+
const ruleAlt = RULE_ALTERNATIVES[ruleId];
|
|
51
|
+
if (ruleAlt) return ruleAlt;
|
|
52
|
+
}
|
|
53
|
+
const haystack = commandArg.toLowerCase();
|
|
54
|
+
for (const [pattern, alt] of COMMAND_ALTERNATIVES) {
|
|
55
|
+
if (pattern.test(haystack)) return alt;
|
|
56
|
+
}
|
|
57
|
+
return "Describe the intended outcome and the specific resource, then request human approval before executing irreversible actions.";
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
INJECTION_PATTERNS,
|
|
61
|
+
detectInjection,
|
|
62
|
+
extractAllStrings,
|
|
63
|
+
getSuggestedAlternative
|
|
64
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { issueFederationToken } from "./oidc-issuer.js";
|
|
2
|
+
const STS_TOKEN_URL = "https://sts.googleapis.com/v1/token";
|
|
3
|
+
const IAM_CREDENTIALS_URL = (saEmail) => `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(saEmail)}:generateAccessToken`;
|
|
4
|
+
async function postJson(url, body, bearer) {
|
|
5
|
+
const res = await fetch(url, {
|
|
6
|
+
method: "POST",
|
|
7
|
+
headers: {
|
|
8
|
+
"Content-Type": "application/json",
|
|
9
|
+
...bearer ? { Authorization: `Bearer ${bearer}` } : {}
|
|
10
|
+
},
|
|
11
|
+
body: JSON.stringify(body),
|
|
12
|
+
signal: AbortSignal.timeout(1e4)
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
const text = await res.text().catch(() => "");
|
|
16
|
+
throw new Error(`gcp-broker: ${url} returned ${res.status}: ${text.slice(0, 500)}`);
|
|
17
|
+
}
|
|
18
|
+
return res.json();
|
|
19
|
+
}
|
|
20
|
+
async function requestGcpCredential(agentId, scopeName, scopeConfig, requestedDurationSeconds) {
|
|
21
|
+
const federationToken = await issueFederationToken({
|
|
22
|
+
agentId,
|
|
23
|
+
audience: scopeConfig.workloadIdentityAudience
|
|
24
|
+
});
|
|
25
|
+
const stsResponse = await postJson(STS_TOKEN_URL, {
|
|
26
|
+
audience: scopeConfig.workloadIdentityAudience,
|
|
27
|
+
grantType: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
28
|
+
requestedTokenType: "urn:ietf:params:oauth:token-type:access_token",
|
|
29
|
+
subjectToken: federationToken,
|
|
30
|
+
subjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
|
|
31
|
+
scope: "https://www.googleapis.com/auth/cloud-platform"
|
|
32
|
+
});
|
|
33
|
+
const lifetimeSeconds = Math.min(requestedDurationSeconds ?? scopeConfig.maxDurationSeconds, scopeConfig.maxDurationSeconds, 3600);
|
|
34
|
+
const impersonated = await postJson(
|
|
35
|
+
IAM_CREDENTIALS_URL(scopeConfig.serviceAccountEmail),
|
|
36
|
+
{ scope: ["https://www.googleapis.com/auth/cloud-platform"], lifetime: `${lifetimeSeconds}s` },
|
|
37
|
+
stsResponse.access_token
|
|
38
|
+
);
|
|
39
|
+
return {
|
|
40
|
+
provider: "gcp",
|
|
41
|
+
gcpAccessToken: impersonated.accessToken,
|
|
42
|
+
serviceAccountEmail: scopeConfig.serviceAccountEmail,
|
|
43
|
+
expiration: new Date(impersonated.expireTime),
|
|
44
|
+
scope: scopeName,
|
|
45
|
+
durationEnforced: true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
requestGcpCredential
|
|
50
|
+
};
|