baselineos 0.2.0-beta.1
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 +17 -0
- package/README.md +198 -0
- package/dist/__evals__/runner.d.ts +2 -0
- package/dist/__evals__/runner.js +14687 -0
- package/dist/__evals__/runner.js.map +1 -0
- package/dist/api/server.d.ts +21 -0
- package/dist/api/server.js +1007 -0
- package/dist/api/server.js.map +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +8427 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/core/agent-bus.d.ts +110 -0
- package/dist/core/agent-bus.js +242 -0
- package/dist/core/agent-bus.js.map +1 -0
- package/dist/core/cache.d.ts +66 -0
- package/dist/core/cache.js +160 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/config.d.ts +1002 -0
- package/dist/core/config.js +429 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/indexer.d.ts +152 -0
- package/dist/core/indexer.js +481 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/llm-tracer.d.ts +2 -0
- package/dist/core/llm-tracer.js +241 -0
- package/dist/core/llm-tracer.js.map +1 -0
- package/dist/core/memory.d.ts +86 -0
- package/dist/core/memory.js +346 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/opa-client.d.ts +51 -0
- package/dist/core/opa-client.js +157 -0
- package/dist/core/opa-client.js.map +1 -0
- package/dist/core/opa-policy-gate.d.ts +133 -0
- package/dist/core/opa-policy-gate.js +454 -0
- package/dist/core/opa-policy-gate.js.map +1 -0
- package/dist/core/orchestrator.d.ts +14 -0
- package/dist/core/orchestrator.js +1297 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/pii-detector.d.ts +82 -0
- package/dist/core/pii-detector.js +126 -0
- package/dist/core/pii-detector.js.map +1 -0
- package/dist/core/rag-engine.d.ts +121 -0
- package/dist/core/rag-engine.js +504 -0
- package/dist/core/rag-engine.js.map +1 -0
- package/dist/core/task-queue.d.ts +69 -0
- package/dist/core/task-queue.js +124 -0
- package/dist/core/task-queue.js.map +1 -0
- package/dist/core/telemetry.d.ts +56 -0
- package/dist/core/telemetry.js +94 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/types.d.ts +328 -0
- package/dist/core/types.js +24 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +12444 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-tracer-CIIujuO-.d.ts +493 -0
- package/dist/mcp/server.d.ts +2651 -0
- package/dist/mcp/server.js +676 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/orchestrator-DF89k_AK.d.ts +506 -0
- package/package.json +157 -0
- package/templates/README.md +7 -0
- package/templates/baseline.config.ts +207 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { OPAClientConfig } from './opa-client.js';
|
|
2
|
+
import { AgentBus } from './agent-bus.js';
|
|
3
|
+
import { Task } from './types.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OPA Policy Gate — SIGNAL-004
|
|
8
|
+
*
|
|
9
|
+
* Runtime governance enforcement using Open Policy Agent.
|
|
10
|
+
* Evaluates three policy rules before and after task execution:
|
|
11
|
+
*
|
|
12
|
+
* baseline/task_allowed — is this task permitted given agent trust + complexity?
|
|
13
|
+
* baseline/agent_trusted — is the agent's trust score above the required threshold?
|
|
14
|
+
* baseline/output_safe — does the task output contain policy violations?
|
|
15
|
+
*
|
|
16
|
+
* Integrates with AgentBus: publishes 'governance:policy-evaluated' and
|
|
17
|
+
* 'governance:violation' for every gate evaluation.
|
|
18
|
+
*
|
|
19
|
+
* Dry-run mode evaluates policies without blocking execution.
|
|
20
|
+
*
|
|
21
|
+
* @license Apache-2.0
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
type PolicyVerdict = 'allow' | 'deny' | 'warn';
|
|
25
|
+
interface PolicyEvaluation {
|
|
26
|
+
policy: string;
|
|
27
|
+
allowed: boolean;
|
|
28
|
+
verdict: PolicyVerdict;
|
|
29
|
+
fallback: boolean;
|
|
30
|
+
latencyMs: number;
|
|
31
|
+
reason?: string;
|
|
32
|
+
}
|
|
33
|
+
interface GateResult {
|
|
34
|
+
passed: boolean;
|
|
35
|
+
blocked: boolean;
|
|
36
|
+
evaluations: PolicyEvaluation[];
|
|
37
|
+
/** true if all results came from the OPA fallback (OPA unreachable) */
|
|
38
|
+
degraded: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* HMAC-SHA256 approval token (SIGNAL-017).
|
|
41
|
+
* Only present when blocked=true and BASELINE_APPROVAL_SECRET is set.
|
|
42
|
+
* Include in the approvalToken field of POST /api/tasks/:id/approve.
|
|
43
|
+
*/
|
|
44
|
+
approvalToken?: string;
|
|
45
|
+
/** Unix ms timestamp when the approval token expires (1 hour from block time). */
|
|
46
|
+
approvalTokenExpires?: number;
|
|
47
|
+
}
|
|
48
|
+
interface EscalationWebhookConfig {
|
|
49
|
+
/** URL to POST to when a policy blocks a task */
|
|
50
|
+
url: string;
|
|
51
|
+
/** Optional headers (e.g. Authorization: Bearer ...) */
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* Base URL of the BaselineOS API server — used to construct the
|
|
55
|
+
* approvalEndpoint field in the webhook payload.
|
|
56
|
+
* Defaults to http://localhost:3141
|
|
57
|
+
*/
|
|
58
|
+
apiBaseUrl?: string;
|
|
59
|
+
}
|
|
60
|
+
interface OPAPolicyGateConfig extends OPAClientConfig {
|
|
61
|
+
/**
|
|
62
|
+
* When true, gate evaluations never block — violations are logged
|
|
63
|
+
* and published to the bus but execution continues.
|
|
64
|
+
*/
|
|
65
|
+
dryRun?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Policies to skip. Useful for staged rollouts.
|
|
68
|
+
* e.g. ['baseline/output_safe']
|
|
69
|
+
*/
|
|
70
|
+
disabledPolicies?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* AgentBus instance for publishing governance events.
|
|
73
|
+
*/
|
|
74
|
+
bus?: AgentBus;
|
|
75
|
+
/**
|
|
76
|
+
* Webhook called when a policy blocks a task.
|
|
77
|
+
* This is the automated escalation gate (SIGNAL-009).
|
|
78
|
+
* Set BASELINE_ALERT_WEBHOOK_URL env var as a fallback.
|
|
79
|
+
*/
|
|
80
|
+
escalationWebhook?: EscalationWebhookConfig;
|
|
81
|
+
}
|
|
82
|
+
declare class OPAPolicyGate {
|
|
83
|
+
private readonly client;
|
|
84
|
+
private readonly dryRun;
|
|
85
|
+
private readonly disabled;
|
|
86
|
+
private readonly bus?;
|
|
87
|
+
private readonly escalationWebhook?;
|
|
88
|
+
/** Rolling window of OPA evaluation durations in ms (SLO-6: p99 < 500ms) */
|
|
89
|
+
private readonly evalDurations;
|
|
90
|
+
constructor(config?: OPAPolicyGateConfig);
|
|
91
|
+
/** Timed wrapper around OPA client.evaluate — records duration for SLO-6 metrics. */
|
|
92
|
+
private timedEvaluate;
|
|
93
|
+
/** Get OPA evaluation duration metrics for Prometheus (SLO-6). */
|
|
94
|
+
getEvalMetrics(): {
|
|
95
|
+
count: number;
|
|
96
|
+
p50Ms: number;
|
|
97
|
+
p95Ms: number;
|
|
98
|
+
p99Ms: number;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Evaluate task + agent policies BEFORE execution.
|
|
102
|
+
* Checks:
|
|
103
|
+
* - baseline/task_allowed (complexity × trust score)
|
|
104
|
+
* - baseline/agent_trusted (trust score × minimum threshold)
|
|
105
|
+
*
|
|
106
|
+
* Returns blocked=true if any policy denies and dryRun=false.
|
|
107
|
+
*/
|
|
108
|
+
preExecution(task: Task, agentId: string, trustScore: number): Promise<GateResult>;
|
|
109
|
+
private _preExecution;
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate output safety policy AFTER execution.
|
|
112
|
+
* Checks:
|
|
113
|
+
* - baseline/output_safe (output content screening)
|
|
114
|
+
*
|
|
115
|
+
* Returns blocked=true only when dryRun=false and the policy denies.
|
|
116
|
+
*/
|
|
117
|
+
postExecution(task: Task, agentId: string, output: {
|
|
118
|
+
content?: string;
|
|
119
|
+
tokensUsed?: number;
|
|
120
|
+
}): Promise<GateResult>;
|
|
121
|
+
private _postExecution;
|
|
122
|
+
isHealthy(): Promise<boolean>;
|
|
123
|
+
get opaUrl(): string;
|
|
124
|
+
get isDryRun(): boolean;
|
|
125
|
+
private buildResult;
|
|
126
|
+
/**
|
|
127
|
+
* Derive minimum trust score based on task complexity + priority.
|
|
128
|
+
* Mirrors the trust tier thresholds in security.ts.
|
|
129
|
+
*/
|
|
130
|
+
private minimumScoreFor;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { type EscalationWebhookConfig, type GateResult, OPAPolicyGate, type OPAPolicyGateConfig, type PolicyEvaluation, type PolicyVerdict };
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { createHmac } from 'crypto';
|
|
2
|
+
import '@opentelemetry/sdk-node';
|
|
3
|
+
import '@opentelemetry/exporter-trace-otlp-http';
|
|
4
|
+
import '@opentelemetry/resources';
|
|
5
|
+
import '@opentelemetry/semantic-conventions';
|
|
6
|
+
import { trace, SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
7
|
+
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __esm = (fn, res) => function __init() {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/core/opa-local-fallback.ts
|
|
19
|
+
var opa_local_fallback_exports = {};
|
|
20
|
+
__export(opa_local_fallback_exports, {
|
|
21
|
+
evaluateAgentTrusted: () => evaluateAgentTrusted,
|
|
22
|
+
evaluateLocally: () => evaluateLocally,
|
|
23
|
+
evaluateOutputSafe: () => evaluateOutputSafe,
|
|
24
|
+
evaluateTaskAllowed: () => evaluateTaskAllowed
|
|
25
|
+
});
|
|
26
|
+
function evaluateTaskAllowed(input) {
|
|
27
|
+
if (input.trust_score < 50) return false;
|
|
28
|
+
if (input.complexity === "epic" && input.trust_score < 85) return false;
|
|
29
|
+
if (input.complexity === "complex" && input.trust_score < 70) return false;
|
|
30
|
+
if (input.priority === "critical" && input.trust_score < 70) return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
function evaluateAgentTrusted(input) {
|
|
34
|
+
if (input.role === "supervisor" && input.trust_score < 70) return false;
|
|
35
|
+
if (input.role === "quality" && input.trust_score < 80) return false;
|
|
36
|
+
return input.trust_score >= 50;
|
|
37
|
+
}
|
|
38
|
+
function evaluateOutputSafe(input) {
|
|
39
|
+
if (input.pii_detected) return false;
|
|
40
|
+
if (input.schema_valid === false) return false;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function evaluateLocally(policyPath, input) {
|
|
44
|
+
const evaluator = LOCAL_EVALUATORS[policyPath];
|
|
45
|
+
if (!evaluator) return void 0;
|
|
46
|
+
try {
|
|
47
|
+
return evaluator(input);
|
|
48
|
+
} catch {
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var LOCAL_EVALUATORS;
|
|
53
|
+
var init_opa_local_fallback = __esm({
|
|
54
|
+
"src/core/opa-local-fallback.ts"() {
|
|
55
|
+
LOCAL_EVALUATORS = {
|
|
56
|
+
"baseline/task_allowed": (input) => evaluateTaskAllowed(input),
|
|
57
|
+
"baseline/agent_trusted": (input) => evaluateAgentTrusted(input),
|
|
58
|
+
"baseline/output_safe": (input) => evaluateOutputSafe(input)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// src/core/opa-client.ts
|
|
64
|
+
var OPAClient = class {
|
|
65
|
+
baseUrl;
|
|
66
|
+
timeoutMs;
|
|
67
|
+
constructor(config = {}) {
|
|
68
|
+
this.baseUrl = (config.url ?? process.env["BASELINE_OPA_URL"] ?? "http://localhost:8181").replace(/\/$/, "");
|
|
69
|
+
this.timeoutMs = config.timeoutMs ?? 2e3;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Evaluate a policy rule against an input document.
|
|
73
|
+
*
|
|
74
|
+
* @param policyPath Slash-separated path, e.g. 'baseline/task_allowed'
|
|
75
|
+
* @param input Arbitrary input document
|
|
76
|
+
* @param fallback Value to return if OPA is unreachable (default: true — fail-open)
|
|
77
|
+
*/
|
|
78
|
+
async evaluate(policyPath, input, fallback = true) {
|
|
79
|
+
const start = Date.now();
|
|
80
|
+
const url = `${this.baseUrl}/v1/data/${policyPath}`;
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "Content-Type": "application/json" },
|
|
85
|
+
body: JSON.stringify({ input }),
|
|
86
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
87
|
+
});
|
|
88
|
+
const latencyMs = Date.now() - start;
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const body = await response.text().catch(() => "");
|
|
91
|
+
throw new Error(`OPA HTTP ${response.status}: ${body}`);
|
|
92
|
+
}
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
return {
|
|
95
|
+
result: data.result ?? fallback,
|
|
96
|
+
decisionId: data.decision_id,
|
|
97
|
+
fallback: false,
|
|
98
|
+
latencyMs
|
|
99
|
+
};
|
|
100
|
+
} catch (err) {
|
|
101
|
+
const latencyMs = Date.now() - start;
|
|
102
|
+
const isTimeout = err instanceof Error && err.name === "TimeoutError";
|
|
103
|
+
const reason = isTimeout ? "timeout" : String(err);
|
|
104
|
+
try {
|
|
105
|
+
const { evaluateLocally: evaluateLocally2 } = await Promise.resolve().then(() => (init_opa_local_fallback(), opa_local_fallback_exports));
|
|
106
|
+
const localResult = evaluateLocally2(policyPath, input);
|
|
107
|
+
if (localResult !== void 0) {
|
|
108
|
+
console.warn(`[OPAClient] Unreachable (${reason}) \u2014 using local fallback for policy "${policyPath}"`);
|
|
109
|
+
return { result: localResult, fallback: true, latencyMs };
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
console.warn(`[OPAClient] Unreachable (${reason}) \u2014 failing open for policy "${policyPath}"`);
|
|
114
|
+
return { result: fallback, fallback: true, latencyMs };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if OPA is healthy and reachable.
|
|
119
|
+
*/
|
|
120
|
+
async healthCheck() {
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(`${this.baseUrl}/health`, {
|
|
123
|
+
signal: AbortSignal.timeout(1e3)
|
|
124
|
+
});
|
|
125
|
+
return response.ok;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
get url() {
|
|
131
|
+
return this.baseUrl;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var tracer = new Proxy({}, {
|
|
135
|
+
get(_target, prop) {
|
|
136
|
+
return trace.getTracer("baselineos", "0.2.0-beta.1")[prop];
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
async function withSpan(name, attributes, fn) {
|
|
140
|
+
return tracer.startActiveSpan(name, { kind: SpanKind.INTERNAL, attributes }, async (span) => {
|
|
141
|
+
try {
|
|
142
|
+
const result = await fn(span);
|
|
143
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
144
|
+
return result;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
span.recordException(err);
|
|
147
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
148
|
+
throw err;
|
|
149
|
+
} finally {
|
|
150
|
+
span.end();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/core/opa-policy-gate.ts
|
|
156
|
+
var OPAPolicyGate = class {
|
|
157
|
+
client;
|
|
158
|
+
dryRun;
|
|
159
|
+
disabled;
|
|
160
|
+
bus;
|
|
161
|
+
escalationWebhook;
|
|
162
|
+
/** Rolling window of OPA evaluation durations in ms (SLO-6: p99 < 500ms) */
|
|
163
|
+
evalDurations = [];
|
|
164
|
+
constructor(config = {}) {
|
|
165
|
+
this.client = new OPAClient({ url: config.url, timeoutMs: config.timeoutMs });
|
|
166
|
+
this.dryRun = config.dryRun ?? false;
|
|
167
|
+
this.disabled = new Set(config.disabledPolicies ?? []);
|
|
168
|
+
this.bus = config.bus;
|
|
169
|
+
this.escalationWebhook = config.escalationWebhook ?? (process.env["BASELINE_ESCALATION_WEBHOOK_URL"] ? { url: process.env["BASELINE_ESCALATION_WEBHOOK_URL"] } : void 0);
|
|
170
|
+
}
|
|
171
|
+
/** Timed wrapper around OPA client.evaluate — records duration for SLO-6 metrics. */
|
|
172
|
+
async timedEvaluate(policy, input) {
|
|
173
|
+
const start = Date.now();
|
|
174
|
+
const res = await this.client.evaluate(policy, input);
|
|
175
|
+
const durationMs = Date.now() - start;
|
|
176
|
+
this.evalDurations.push(durationMs);
|
|
177
|
+
if (this.evalDurations.length > 1e3) this.evalDurations.shift();
|
|
178
|
+
return res;
|
|
179
|
+
}
|
|
180
|
+
/** Get OPA evaluation duration metrics for Prometheus (SLO-6). */
|
|
181
|
+
getEvalMetrics() {
|
|
182
|
+
if (this.evalDurations.length === 0) return { count: 0, p50Ms: 0, p95Ms: 0, p99Ms: 0 };
|
|
183
|
+
const sorted = [...this.evalDurations].sort((a, b) => a - b);
|
|
184
|
+
const p = (pct) => sorted[Math.min(Math.floor(sorted.length * pct), sorted.length - 1)];
|
|
185
|
+
return { count: sorted.length, p50Ms: p(0.5), p95Ms: p(0.95), p99Ms: p(0.99) };
|
|
186
|
+
}
|
|
187
|
+
// ─── Pre-execution gate ───────────────────────────────────────────────────
|
|
188
|
+
/**
|
|
189
|
+
* Evaluate task + agent policies BEFORE execution.
|
|
190
|
+
* Checks:
|
|
191
|
+
* - baseline/task_allowed (complexity × trust score)
|
|
192
|
+
* - baseline/agent_trusted (trust score × minimum threshold)
|
|
193
|
+
*
|
|
194
|
+
* Returns blocked=true if any policy denies and dryRun=false.
|
|
195
|
+
*/
|
|
196
|
+
async preExecution(task, agentId, trustScore) {
|
|
197
|
+
return withSpan("task.policy.pre", {
|
|
198
|
+
"task.id": task.id,
|
|
199
|
+
"agent.id": agentId,
|
|
200
|
+
"agent.trust_score": trustScore
|
|
201
|
+
}, async (span) => {
|
|
202
|
+
const result = await this._preExecution(task, agentId, trustScore);
|
|
203
|
+
span.setAttribute("policy.blocked", result.blocked);
|
|
204
|
+
span.setAttribute("policy.degraded", result.degraded);
|
|
205
|
+
return result;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async _preExecution(task, agentId, trustScore) {
|
|
209
|
+
const evals = [];
|
|
210
|
+
if (!this.disabled.has("baseline/task_allowed")) {
|
|
211
|
+
const res = await this.timedEvaluate("baseline/task_allowed", {
|
|
212
|
+
task_id: task.id,
|
|
213
|
+
complexity: task.complexity,
|
|
214
|
+
priority: task.priority,
|
|
215
|
+
required_capabilities: task.requiredCapabilities,
|
|
216
|
+
agent_id: agentId,
|
|
217
|
+
trust_score: trustScore
|
|
218
|
+
});
|
|
219
|
+
evals.push({
|
|
220
|
+
policy: "baseline/task_allowed",
|
|
221
|
+
allowed: res.result,
|
|
222
|
+
verdict: res.result ? "allow" : "deny",
|
|
223
|
+
fallback: res.fallback,
|
|
224
|
+
latencyMs: res.latencyMs,
|
|
225
|
+
reason: res.result ? void 0 : "Policy denied task based on trust/complexity"
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (!this.disabled.has("baseline/agent_trusted")) {
|
|
229
|
+
const minScore = this.minimumScoreFor(task);
|
|
230
|
+
const res = await this.timedEvaluate("baseline/agent_trusted", {
|
|
231
|
+
agent_id: agentId,
|
|
232
|
+
trust_score: trustScore,
|
|
233
|
+
minimum_score: minScore,
|
|
234
|
+
task_complexity: task.complexity,
|
|
235
|
+
task_priority: task.priority
|
|
236
|
+
});
|
|
237
|
+
evals.push({
|
|
238
|
+
policy: "baseline/agent_trusted",
|
|
239
|
+
allowed: res.result,
|
|
240
|
+
verdict: res.result ? "allow" : "deny",
|
|
241
|
+
fallback: res.fallback,
|
|
242
|
+
latencyMs: res.latencyMs,
|
|
243
|
+
reason: res.result ? void 0 : `Agent trust score ${trustScore} below minimum ${minScore}`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return this.buildResult(evals, "pre-execution", task, agentId);
|
|
247
|
+
}
|
|
248
|
+
// ─── Post-execution gate ──────────────────────────────────────────────────
|
|
249
|
+
/**
|
|
250
|
+
* Evaluate output safety policy AFTER execution.
|
|
251
|
+
* Checks:
|
|
252
|
+
* - baseline/output_safe (output content screening)
|
|
253
|
+
*
|
|
254
|
+
* Returns blocked=true only when dryRun=false and the policy denies.
|
|
255
|
+
*/
|
|
256
|
+
async postExecution(task, agentId, output) {
|
|
257
|
+
return withSpan("task.policy.post", {
|
|
258
|
+
"task.id": task.id,
|
|
259
|
+
"agent.id": agentId,
|
|
260
|
+
"output.tokens": output.tokensUsed ?? 0
|
|
261
|
+
}, async (span) => {
|
|
262
|
+
const result = await this._postExecution(task, agentId, output);
|
|
263
|
+
span.setAttribute("policy.blocked", result.blocked);
|
|
264
|
+
span.setAttribute("policy.degraded", result.degraded);
|
|
265
|
+
return result;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
async _postExecution(task, agentId, output) {
|
|
269
|
+
const evals = [];
|
|
270
|
+
if (!this.disabled.has("baseline/output_safe")) {
|
|
271
|
+
const res = await this.timedEvaluate("baseline/output_safe", {
|
|
272
|
+
task_id: task.id,
|
|
273
|
+
agent_id: agentId,
|
|
274
|
+
output: (output.content ?? "").slice(0, 2e3),
|
|
275
|
+
tokens_used: output.tokensUsed ?? 0
|
|
276
|
+
});
|
|
277
|
+
evals.push({
|
|
278
|
+
policy: "baseline/output_safe",
|
|
279
|
+
allowed: res.result,
|
|
280
|
+
verdict: res.result ? "allow" : "deny",
|
|
281
|
+
fallback: res.fallback,
|
|
282
|
+
latencyMs: res.latencyMs,
|
|
283
|
+
reason: res.result ? void 0 : "Output failed safety policy check"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return this.buildResult(evals, "post-execution", task, agentId);
|
|
287
|
+
}
|
|
288
|
+
// ─── Health ───────────────────────────────────────────────────────────────
|
|
289
|
+
async isHealthy() {
|
|
290
|
+
return this.client.healthCheck();
|
|
291
|
+
}
|
|
292
|
+
get opaUrl() {
|
|
293
|
+
return this.client.url;
|
|
294
|
+
}
|
|
295
|
+
get isDryRun() {
|
|
296
|
+
return this.dryRun;
|
|
297
|
+
}
|
|
298
|
+
// ─── Internals ────────────────────────────────────────────────────────────
|
|
299
|
+
buildResult(evals, phase, task, agentId) {
|
|
300
|
+
const violations = evals.filter((e) => !e.allowed);
|
|
301
|
+
const degraded = evals.length > 0 && evals.every((e) => e.fallback);
|
|
302
|
+
const blocked = !this.dryRun && violations.length > 0;
|
|
303
|
+
const passed = violations.length === 0;
|
|
304
|
+
let approvalToken;
|
|
305
|
+
let approvalTokenExpires;
|
|
306
|
+
const approvalSecret = process.env["BASELINE_APPROVAL_SECRET"];
|
|
307
|
+
if (blocked && approvalSecret) {
|
|
308
|
+
approvalTokenExpires = Date.now() + 36e5;
|
|
309
|
+
approvalToken = createHmac("sha256", approvalSecret).update(`${task.id}:${approvalTokenExpires}`).digest("hex");
|
|
310
|
+
}
|
|
311
|
+
if (this.bus) {
|
|
312
|
+
this.bus.broadcast("system", "governance:policy-evaluated", {
|
|
313
|
+
phase,
|
|
314
|
+
taskId: task.id,
|
|
315
|
+
agentId,
|
|
316
|
+
passed,
|
|
317
|
+
blocked,
|
|
318
|
+
degraded,
|
|
319
|
+
evaluations: evals
|
|
320
|
+
}, "normal");
|
|
321
|
+
for (const v of violations) {
|
|
322
|
+
this.bus.broadcast("system", "governance:violation", {
|
|
323
|
+
phase,
|
|
324
|
+
taskId: task.id,
|
|
325
|
+
agentId,
|
|
326
|
+
policy: v.policy,
|
|
327
|
+
reason: v.reason,
|
|
328
|
+
blocked,
|
|
329
|
+
dryRun: this.dryRun
|
|
330
|
+
}, "high");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (violations.length > 0) {
|
|
334
|
+
const mode = this.dryRun ? "[DRY RUN]" : "";
|
|
335
|
+
for (const v of violations) {
|
|
336
|
+
console.warn(`[OPAPolicyGate]${mode} ${phase} policy denied: ${v.policy} \u2014 ${v.reason}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (blocked && this.escalationWebhook) {
|
|
340
|
+
const apiBase = this.escalationWebhook.apiBaseUrl ?? "http://localhost:3141";
|
|
341
|
+
const expiresAt = approvalTokenExpires ?? Date.now() + 36e5;
|
|
342
|
+
const payload = {
|
|
343
|
+
taskId: task.id,
|
|
344
|
+
agentId,
|
|
345
|
+
phase,
|
|
346
|
+
policy: violations[0]?.policy ?? "unknown",
|
|
347
|
+
reason: violations[0]?.reason ?? "Policy gate blocked execution",
|
|
348
|
+
approvalEndpoint: `${apiBase}/api/tasks/${task.id}/approve`,
|
|
349
|
+
approvalToken,
|
|
350
|
+
// SIGNAL-017: include token so webhook receiver can approve
|
|
351
|
+
expiresAt,
|
|
352
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
353
|
+
};
|
|
354
|
+
fetch(this.escalationWebhook.url, {
|
|
355
|
+
method: "POST",
|
|
356
|
+
headers: { "Content-Type": "application/json", ...this.escalationWebhook.headers },
|
|
357
|
+
body: JSON.stringify(payload),
|
|
358
|
+
signal: AbortSignal.timeout(3e3)
|
|
359
|
+
}).catch((err) => {
|
|
360
|
+
console.warn(`[OPAPolicyGate] Escalation webhook failed: ${err}`);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return { passed, blocked, evaluations: evals, degraded, approvalToken, approvalTokenExpires };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Derive minimum trust score based on task complexity + priority.
|
|
367
|
+
* Mirrors the trust tier thresholds in security.ts.
|
|
368
|
+
*/
|
|
369
|
+
minimumScoreFor(task) {
|
|
370
|
+
if (task.priority === "critical") return 80;
|
|
371
|
+
switch (task.complexity) {
|
|
372
|
+
case "epic":
|
|
373
|
+
return 85;
|
|
374
|
+
case "complex":
|
|
375
|
+
return 70;
|
|
376
|
+
case "moderate":
|
|
377
|
+
return 60;
|
|
378
|
+
default:
|
|
379
|
+
return 50;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* OPA Local Fallback — Offline Policy Evaluation
|
|
385
|
+
*
|
|
386
|
+
* TypeScript mirror of the 3 OPA .rego policies for offline enforcement.
|
|
387
|
+
* When OPA is unreachable, the OPAPolicyGate can use these evaluators
|
|
388
|
+
* instead of blindly failing open. This ensures governance primitives
|
|
389
|
+
* work without network connectivity (spec: README.md line 88).
|
|
390
|
+
*
|
|
391
|
+
* Policies mirrored:
|
|
392
|
+
* baseline/task_allowed — from policies/baseline/task.rego
|
|
393
|
+
* baseline/agent_trusted — from policies/baseline/agent.rego
|
|
394
|
+
* baseline/output_safe — from policies/baseline/output.rego
|
|
395
|
+
*
|
|
396
|
+
* @license Apache-2.0
|
|
397
|
+
*/
|
|
398
|
+
/**
|
|
399
|
+
* OPA Client — SIGNAL-004
|
|
400
|
+
*
|
|
401
|
+
* Lightweight HTTP client for the Open Policy Agent REST API.
|
|
402
|
+
* Zero external dependencies — uses native fetch (Node 18+).
|
|
403
|
+
*
|
|
404
|
+
* Fail-open by default: if OPA is unreachable, evaluation returns
|
|
405
|
+
* the provided fallback value and emits a warning. This prevents
|
|
406
|
+
* OPA downtime from blocking agent execution.
|
|
407
|
+
*
|
|
408
|
+
* @license Apache-2.0
|
|
409
|
+
*/
|
|
410
|
+
/**
|
|
411
|
+
* BaselineOS OpenTelemetry — SIGNAL-012
|
|
412
|
+
*
|
|
413
|
+
* Distributed tracing for the multi-agent orchestration layer.
|
|
414
|
+
* Initializes the OTel Node SDK and exports a shared tracer.
|
|
415
|
+
*
|
|
416
|
+
* Spans emitted:
|
|
417
|
+
* task.execute root span per executeTask() call
|
|
418
|
+
* task.policy.pre OPA pre-execution gate
|
|
419
|
+
* task.policy.post OPA post-execution gate
|
|
420
|
+
* task.perform performExecution() step loop
|
|
421
|
+
* task.self_verify selfVerify() LLM call
|
|
422
|
+
* task.review conductReview() LLM call
|
|
423
|
+
*
|
|
424
|
+
* Trace context is propagated through AgentBus messages via the
|
|
425
|
+
* optional `traceContext` field (W3C traceparent format).
|
|
426
|
+
*
|
|
427
|
+
* Configuration:
|
|
428
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)
|
|
429
|
+
* OTEL_SERVICE_NAME service name override (default: baselineos)
|
|
430
|
+
* BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)
|
|
431
|
+
*
|
|
432
|
+
* @license Apache-2.0
|
|
433
|
+
*/
|
|
434
|
+
/**
|
|
435
|
+
* OPA Policy Gate — SIGNAL-004
|
|
436
|
+
*
|
|
437
|
+
* Runtime governance enforcement using Open Policy Agent.
|
|
438
|
+
* Evaluates three policy rules before and after task execution:
|
|
439
|
+
*
|
|
440
|
+
* baseline/task_allowed — is this task permitted given agent trust + complexity?
|
|
441
|
+
* baseline/agent_trusted — is the agent's trust score above the required threshold?
|
|
442
|
+
* baseline/output_safe — does the task output contain policy violations?
|
|
443
|
+
*
|
|
444
|
+
* Integrates with AgentBus: publishes 'governance:policy-evaluated' and
|
|
445
|
+
* 'governance:violation' for every gate evaluation.
|
|
446
|
+
*
|
|
447
|
+
* Dry-run mode evaluates policies without blocking execution.
|
|
448
|
+
*
|
|
449
|
+
* @license Apache-2.0
|
|
450
|
+
*/
|
|
451
|
+
|
|
452
|
+
export { OPAPolicyGate };
|
|
453
|
+
//# sourceMappingURL=opa-policy-gate.js.map
|
|
454
|
+
//# sourceMappingURL=opa-policy-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/opa-local-fallback.ts","../../src/core/opa-client.ts","../../src/core/telemetry.ts","../../src/core/opa-policy-gate.ts"],"names":["evaluateLocally"],"mappings":"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","file":"opa-policy-gate.js","sourcesContent":["/**\n * OPA Local Fallback — Offline Policy Evaluation\n *\n * TypeScript mirror of the 3 OPA .rego policies for offline enforcement.\n * When OPA is unreachable, the OPAPolicyGate can use these evaluators\n * instead of blindly failing open. This ensures governance primitives\n * work without network connectivity (spec: README.md line 88).\n *\n * Policies mirrored:\n * baseline/task_allowed — from policies/baseline/task.rego\n * baseline/agent_trusted — from policies/baseline/agent.rego\n * baseline/output_safe — from policies/baseline/output.rego\n *\n * @license Apache-2.0\n */\n\n// ─── task_allowed (mirrors task.rego) ────────────────────────────────────────\n\nexport function evaluateTaskAllowed(input: {\n trust_score: number;\n complexity: string;\n priority: string;\n}): boolean {\n // Agent suspended below 50\n if (input.trust_score < 50) return false;\n\n // Epic requires >= 85\n if (input.complexity === 'epic' && input.trust_score < 85) return false;\n\n // Complex requires >= 70\n if (input.complexity === 'complex' && input.trust_score < 70) return false;\n\n // Critical priority requires >= 70\n if (input.priority === 'critical' && input.trust_score < 70) return false;\n\n return true;\n}\n\n// ─── agent_trusted (mirrors agent.rego) ──────────────────────────────────────\n\nexport function evaluateAgentTrusted(input: {\n trust_score: number;\n role?: string;\n}): boolean {\n // Supervisors need trust >= 70\n if (input.role === 'supervisor' && input.trust_score < 70) return false;\n\n // Quality agents need trust >= 80\n if (input.role === 'quality' && input.trust_score < 80) return false;\n\n // All agents need trust >= 50\n return input.trust_score >= 50;\n}\n\n// ─── output_safe (mirrors output.rego) ───────────────────────────────────────\n\nexport function evaluateOutputSafe(input: {\n output?: string;\n pii_detected?: boolean;\n schema_valid?: boolean;\n}): boolean {\n // PII in output is never safe\n if (input.pii_detected) return false;\n\n // Schema validation failure is not safe\n if (input.schema_valid === false) return false;\n\n return true;\n}\n\n// ─── Dispatcher ──────────────────────────────────────────────────────────────\n\nconst LOCAL_EVALUATORS: Record<string, (input: Record<string, unknown>) => boolean> = {\n 'baseline/task_allowed': (input) => evaluateTaskAllowed(input as Parameters<typeof evaluateTaskAllowed>[0]),\n 'baseline/agent_trusted': (input) => evaluateAgentTrusted(input as Parameters<typeof evaluateAgentTrusted>[0]),\n 'baseline/output_safe': (input) => evaluateOutputSafe(input as Parameters<typeof evaluateOutputSafe>[0]),\n};\n\n/**\n * Evaluate a policy locally. Returns undefined if no local evaluator exists\n * for the given policy path (caller should fall back to fail-open).\n */\nexport function evaluateLocally(policyPath: string, input: Record<string, unknown>): boolean | undefined {\n const evaluator = LOCAL_EVALUATORS[policyPath];\n if (!evaluator) return undefined;\n try {\n return evaluator(input);\n } catch {\n return undefined;\n }\n}\n","/**\n * OPA Client — SIGNAL-004\n *\n * Lightweight HTTP client for the Open Policy Agent REST API.\n * Zero external dependencies — uses native fetch (Node 18+).\n *\n * Fail-open by default: if OPA is unreachable, evaluation returns\n * the provided fallback value and emits a warning. This prevents\n * OPA downtime from blocking agent execution.\n *\n * @license Apache-2.0\n */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface OPAInput {\n [key: string]: unknown;\n}\n\nexport interface OPAEvaluationResult<T = unknown> {\n /** Policy decision result */\n result: T;\n /** OPA decision ID (present when OPA decision logging is enabled) */\n decisionId?: string;\n /** Whether this result came from the fallback (OPA was unreachable) */\n fallback: boolean;\n /** Evaluation latency in ms */\n latencyMs: number;\n}\n\nexport interface OPAClientConfig {\n /** OPA base URL. Defaults to BASELINE_OPA_URL env var or http://localhost:8181 */\n url?: string;\n /** Request timeout in ms. Default: 2000 */\n timeoutMs?: number;\n}\n\n// ─── OPAClient ────────────────────────────────────────────────────────────────\n\nexport class OPAClient {\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: OPAClientConfig = {}) {\n this.baseUrl = (\n config.url ??\n process.env['BASELINE_OPA_URL'] ??\n 'http://localhost:8181'\n ).replace(/\\/$/, '');\n this.timeoutMs = config.timeoutMs ?? 2000;\n }\n\n /**\n * Evaluate a policy rule against an input document.\n *\n * @param policyPath Slash-separated path, e.g. 'baseline/task_allowed'\n * @param input Arbitrary input document\n * @param fallback Value to return if OPA is unreachable (default: true — fail-open)\n */\n async evaluate<T = boolean>(\n policyPath: string,\n input: OPAInput,\n fallback: T = true as unknown as T,\n ): Promise<OPAEvaluationResult<T>> {\n const start = Date.now();\n const url = `${this.baseUrl}/v1/data/${policyPath}`;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ input }),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n\n const latencyMs = Date.now() - start;\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(`OPA HTTP ${response.status}: ${body}`);\n }\n\n const data = await response.json() as { result?: T; decision_id?: string };\n return {\n result: data.result ?? fallback,\n decisionId: data.decision_id,\n fallback: false,\n latencyMs,\n };\n } catch (err) {\n const latencyMs = Date.now() - start;\n const isTimeout = err instanceof Error && err.name === 'TimeoutError';\n const reason = isTimeout ? 'timeout' : String(err);\n\n // Try local policy evaluation before failing open\n try {\n const { evaluateLocally } = await import('./opa-local-fallback.js');\n const localResult = evaluateLocally(policyPath, input);\n if (localResult !== undefined) {\n console.warn(`[OPAClient] Unreachable (${reason}) — using local fallback for policy \"${policyPath}\"`);\n return { result: localResult as unknown as T, fallback: true, latencyMs };\n }\n } catch {\n // Local fallback unavailable — continue to fail-open\n }\n\n console.warn(`[OPAClient] Unreachable (${reason}) — failing open for policy \"${policyPath}\"`);\n return { result: fallback, fallback: true, latencyMs };\n }\n }\n\n /**\n * Check if OPA is healthy and reachable.\n */\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/health`, {\n signal: AbortSignal.timeout(1000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n get url(): string {\n return this.baseUrl;\n }\n}\n","/**\n * BaselineOS OpenTelemetry — SIGNAL-012\n *\n * Distributed tracing for the multi-agent orchestration layer.\n * Initializes the OTel Node SDK and exports a shared tracer.\n *\n * Spans emitted:\n * task.execute root span per executeTask() call\n * task.policy.pre OPA pre-execution gate\n * task.policy.post OPA post-execution gate\n * task.perform performExecution() step loop\n * task.self_verify selfVerify() LLM call\n * task.review conductReview() LLM call\n *\n * Trace context is propagated through AgentBus messages via the\n * optional `traceContext` field (W3C traceparent format).\n *\n * Configuration:\n * OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)\n * OTEL_SERVICE_NAME service name override (default: baselineos)\n * BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)\n *\n * @license Apache-2.0\n */\n\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { trace, context, propagation, SpanStatusCode, SpanKind } from '@opentelemetry/api';\nimport type { Span, Tracer, Context } from '@opentelemetry/api';\n\nexport { SpanStatusCode, SpanKind };\nexport type { Span, Tracer, Context };\n\n// ─── SDK init ────────────────────────────────────────────────────────────────\n\nlet _sdk: NodeSDK | null = null;\n\n/**\n * Initialize the OTel SDK. Safe to call multiple times — subsequent calls\n * are no-ops. Should be called once at process startup before any spans.\n *\n * Silently disabled when BASELINE_OTEL_DISABLED=1.\n */\nexport function initTelemetry(): void {\n if (_sdk || process.env['BASELINE_OTEL_DISABLED'] === '1') return;\n\n const endpoint =\n process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] ?? 'http://localhost:4318';\n\n const exporter = new OTLPTraceExporter({\n url: `${endpoint}/v1/traces`,\n });\n\n _sdk = new NodeSDK({\n resource: resourceFromAttributes({\n [ATTR_SERVICE_NAME]: process.env['OTEL_SERVICE_NAME'] ?? 'baselineos',\n [ATTR_SERVICE_VERSION]: '0.2.0-beta.1',\n }),\n traceExporter: exporter,\n // W3C traceparent propagation is the default — no explicit config needed\n });\n\n _sdk.start();\n\n process.on('SIGTERM', () => { _sdk?.shutdown().catch(() => {}); });\n process.on('SIGINT', () => { _sdk?.shutdown().catch(() => {}); });\n}\n\n/** Gracefully shut down the SDK and flush pending spans. */\nexport async function shutdownTelemetry(): Promise<void> {\n await _sdk?.shutdown();\n _sdk = null;\n}\n\n// ─── Tracer ───────────────────────────────────────────────────────────────────\n\nexport const tracer: Tracer = new Proxy({} as Tracer, {\n get(_target, prop) {\n return trace.getTracer('baselineos', '0.2.0-beta.1')[prop as keyof Tracer];\n },\n});\n\n// ─── Trace context helpers ───────────────────────────────────────────────────\n\n/**\n * Serialize the active W3C trace context to a string for propagation\n * through AgentBus messages.\n * Returns undefined when there is no active span.\n */\nexport function extractTraceContext(): string | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n return carrier['traceparent'];\n}\n\n/**\n * Restore a serialized trace context as the active context.\n * Use as the second argument to `context.with()` before creating child spans.\n */\nexport function injectTraceContext(traceparent: string): Context {\n const carrier: Record<string, string> = { traceparent };\n return propagation.extract(context.active(), carrier);\n}\n\n// ─── Span wrapper ─────────────────────────────────────────────────────────────\n\n/**\n * Run an async function inside a named span.\n * Sets ERROR status and records exception on throw.\n */\nexport async function withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n return tracer.startActiveSpan(name, { kind: SpanKind.INTERNAL, attributes }, async (span) => {\n try {\n const result = await fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message });\n throw err;\n } finally {\n span.end();\n }\n });\n}\n","/**\n * OPA Policy Gate — SIGNAL-004\n *\n * Runtime governance enforcement using Open Policy Agent.\n * Evaluates three policy rules before and after task execution:\n *\n * baseline/task_allowed — is this task permitted given agent trust + complexity?\n * baseline/agent_trusted — is the agent's trust score above the required threshold?\n * baseline/output_safe — does the task output contain policy violations?\n *\n * Integrates with AgentBus: publishes 'governance:policy-evaluated' and\n * 'governance:violation' for every gate evaluation.\n *\n * Dry-run mode evaluates policies without blocking execution.\n *\n * @license Apache-2.0\n */\n\nimport { createHmac } from 'crypto';\nimport { OPAClient } from './opa-client.js';\nimport type { OPAClientConfig } from './opa-client.js';\nimport type { AgentBus } from './agent-bus.js';\nimport type { Task } from './types.js';\nimport { withSpan } from './telemetry.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type PolicyVerdict = 'allow' | 'deny' | 'warn';\n\nexport interface PolicyEvaluation {\n policy: string;\n allowed: boolean;\n verdict: PolicyVerdict;\n fallback: boolean;\n latencyMs: number;\n reason?: string;\n}\n\nexport interface GateResult {\n passed: boolean;\n blocked: boolean;\n evaluations: PolicyEvaluation[];\n /** true if all results came from the OPA fallback (OPA unreachable) */\n degraded: boolean;\n /**\n * HMAC-SHA256 approval token (SIGNAL-017).\n * Only present when blocked=true and BASELINE_APPROVAL_SECRET is set.\n * Include in the approvalToken field of POST /api/tasks/:id/approve.\n */\n approvalToken?: string;\n /** Unix ms timestamp when the approval token expires (1 hour from block time). */\n approvalTokenExpires?: number;\n}\n\nexport interface EscalationWebhookConfig {\n /** URL to POST to when a policy blocks a task */\n url: string;\n /** Optional headers (e.g. Authorization: Bearer ...) */\n headers?: Record<string, string>;\n /**\n * Base URL of the BaselineOS API server — used to construct the\n * approvalEndpoint field in the webhook payload.\n * Defaults to http://localhost:3141\n */\n apiBaseUrl?: string;\n}\n\nexport interface OPAPolicyGateConfig extends OPAClientConfig {\n /**\n * When true, gate evaluations never block — violations are logged\n * and published to the bus but execution continues.\n */\n dryRun?: boolean;\n /**\n * Policies to skip. Useful for staged rollouts.\n * e.g. ['baseline/output_safe']\n */\n disabledPolicies?: string[];\n /**\n * AgentBus instance for publishing governance events.\n */\n bus?: AgentBus;\n /**\n * Webhook called when a policy blocks a task.\n * This is the automated escalation gate (SIGNAL-009).\n * Set BASELINE_ALERT_WEBHOOK_URL env var as a fallback.\n */\n escalationWebhook?: EscalationWebhookConfig;\n}\n\n// ─── OPAPolicyGate ────────────────────────────────────────────────────────────\n\nexport class OPAPolicyGate {\n private readonly client: OPAClient;\n private readonly dryRun: boolean;\n private readonly disabled: Set<string>;\n private readonly bus?: AgentBus;\n private readonly escalationWebhook?: EscalationWebhookConfig;\n /** Rolling window of OPA evaluation durations in ms (SLO-6: p99 < 500ms) */\n private readonly evalDurations: number[] = [];\n\n constructor(config: OPAPolicyGateConfig = {}) {\n this.client = new OPAClient({ url: config.url, timeoutMs: config.timeoutMs });\n this.dryRun = config.dryRun ?? false;\n this.disabled = new Set(config.disabledPolicies ?? []);\n this.bus = config.bus;\n this.escalationWebhook = config.escalationWebhook ?? (\n process.env['BASELINE_ESCALATION_WEBHOOK_URL']\n ? { url: process.env['BASELINE_ESCALATION_WEBHOOK_URL'] }\n : undefined\n );\n }\n\n /** Timed wrapper around OPA client.evaluate — records duration for SLO-6 metrics. */\n private async timedEvaluate<T>(policy: string, input: Record<string, unknown>) {\n const start = Date.now();\n const res = await this.client.evaluate<T>(policy, input);\n const durationMs = Date.now() - start;\n this.evalDurations.push(durationMs);\n if (this.evalDurations.length > 1000) this.evalDurations.shift();\n return res;\n }\n\n /** Get OPA evaluation duration metrics for Prometheus (SLO-6). */\n getEvalMetrics(): { count: number; p50Ms: number; p95Ms: number; p99Ms: number } {\n if (this.evalDurations.length === 0) return { count: 0, p50Ms: 0, p95Ms: 0, p99Ms: 0 };\n const sorted = [...this.evalDurations].sort((a, b) => a - b);\n const p = (pct: number) => sorted[Math.min(Math.floor(sorted.length * pct), sorted.length - 1)]!;\n return { count: sorted.length, p50Ms: p(0.5), p95Ms: p(0.95), p99Ms: p(0.99) };\n }\n\n // ─── Pre-execution gate ───────────────────────────────────────────────────\n\n /**\n * Evaluate task + agent policies BEFORE execution.\n * Checks:\n * - baseline/task_allowed (complexity × trust score)\n * - baseline/agent_trusted (trust score × minimum threshold)\n *\n * Returns blocked=true if any policy denies and dryRun=false.\n */\n async preExecution(task: Task, agentId: string, trustScore: number): Promise<GateResult> {\n return withSpan('task.policy.pre', {\n 'task.id': task.id,\n 'agent.id': agentId,\n 'agent.trust_score': trustScore,\n }, async (span) => {\n const result = await this._preExecution(task, agentId, trustScore);\n span.setAttribute('policy.blocked', result.blocked);\n span.setAttribute('policy.degraded', result.degraded);\n return result;\n });\n }\n\n private async _preExecution(task: Task, agentId: string, trustScore: number): Promise<GateResult> {\n const evals: PolicyEvaluation[] = [];\n\n // baseline/task_allowed\n if (!this.disabled.has('baseline/task_allowed')) {\n const res = await this.timedEvaluate<boolean>('baseline/task_allowed', {\n task_id: task.id,\n complexity: task.complexity,\n priority: task.priority,\n required_capabilities: task.requiredCapabilities,\n agent_id: agentId,\n trust_score: trustScore,\n });\n\n evals.push({\n policy: 'baseline/task_allowed',\n allowed: res.result,\n verdict: res.result ? 'allow' : 'deny',\n fallback: res.fallback,\n latencyMs: res.latencyMs,\n reason: res.result ? undefined : 'Policy denied task based on trust/complexity',\n });\n }\n\n // baseline/agent_trusted\n if (!this.disabled.has('baseline/agent_trusted')) {\n const minScore = this.minimumScoreFor(task);\n const res = await this.timedEvaluate<boolean>('baseline/agent_trusted', {\n agent_id: agentId,\n trust_score: trustScore,\n minimum_score: minScore,\n task_complexity: task.complexity,\n task_priority: task.priority,\n });\n\n evals.push({\n policy: 'baseline/agent_trusted',\n allowed: res.result,\n verdict: res.result ? 'allow' : 'deny',\n fallback: res.fallback,\n latencyMs: res.latencyMs,\n reason: res.result ? undefined : `Agent trust score ${trustScore} below minimum ${minScore}`,\n });\n }\n\n return this.buildResult(evals, 'pre-execution', task, agentId);\n }\n\n // ─── Post-execution gate ──────────────────────────────────────────────────\n\n /**\n * Evaluate output safety policy AFTER execution.\n * Checks:\n * - baseline/output_safe (output content screening)\n *\n * Returns blocked=true only when dryRun=false and the policy denies.\n */\n async postExecution(\n task: Task,\n agentId: string,\n output: { content?: string; tokensUsed?: number },\n ): Promise<GateResult> {\n return withSpan('task.policy.post', {\n 'task.id': task.id,\n 'agent.id': agentId,\n 'output.tokens': output.tokensUsed ?? 0,\n }, async (span) => {\n const result = await this._postExecution(task, agentId, output);\n span.setAttribute('policy.blocked', result.blocked);\n span.setAttribute('policy.degraded', result.degraded);\n return result;\n });\n }\n\n private async _postExecution(\n task: Task,\n agentId: string,\n output: { content?: string; tokensUsed?: number },\n ): Promise<GateResult> {\n const evals: PolicyEvaluation[] = [];\n\n if (!this.disabled.has('baseline/output_safe')) {\n const res = await this.timedEvaluate<boolean>('baseline/output_safe', {\n task_id: task.id,\n agent_id: agentId,\n output: (output.content ?? '').slice(0, 2000),\n tokens_used: output.tokensUsed ?? 0,\n });\n\n evals.push({\n policy: 'baseline/output_safe',\n allowed: res.result,\n verdict: res.result ? 'allow' : 'deny',\n fallback: res.fallback,\n latencyMs: res.latencyMs,\n reason: res.result ? undefined : 'Output failed safety policy check',\n });\n }\n\n return this.buildResult(evals, 'post-execution', task, agentId);\n }\n\n // ─── Health ───────────────────────────────────────────────────────────────\n\n async isHealthy(): Promise<boolean> {\n return this.client.healthCheck();\n }\n\n get opaUrl(): string {\n return this.client.url;\n }\n\n get isDryRun(): boolean {\n return this.dryRun;\n }\n\n // ─── Internals ────────────────────────────────────────────────────────────\n\n private buildResult(\n evals: PolicyEvaluation[],\n phase: string,\n task: Task,\n agentId: string,\n ): GateResult {\n const violations = evals.filter((e) => !e.allowed);\n const degraded = evals.length > 0 && evals.every((e) => e.fallback);\n const blocked = !this.dryRun && violations.length > 0;\n const passed = violations.length === 0;\n\n // Generate HMAC approval token when blocked (SIGNAL-017)\n let approvalToken: string | undefined;\n let approvalTokenExpires: number | undefined;\n const approvalSecret = process.env['BASELINE_APPROVAL_SECRET'];\n if (blocked && approvalSecret) {\n approvalTokenExpires = Date.now() + 3_600_000;\n approvalToken = createHmac('sha256', approvalSecret)\n .update(`${task.id}:${approvalTokenExpires}`)\n .digest('hex');\n }\n\n // Publish to bus\n if (this.bus) {\n this.bus.broadcast('system', 'governance:policy-evaluated', {\n phase,\n taskId: task.id,\n agentId,\n passed,\n blocked,\n degraded,\n evaluations: evals,\n }, 'normal');\n\n for (const v of violations) {\n this.bus.broadcast('system', 'governance:violation', {\n phase,\n taskId: task.id,\n agentId,\n policy: v.policy,\n reason: v.reason,\n blocked,\n dryRun: this.dryRun,\n }, 'high');\n }\n }\n\n if (violations.length > 0) {\n const mode = this.dryRun ? '[DRY RUN]' : '';\n for (const v of violations) {\n console.warn(`[OPAPolicyGate]${mode} ${phase} policy denied: ${v.policy} — ${v.reason}`);\n }\n }\n\n // Fire escalation webhook when blocked (SIGNAL-009 / SIGNAL-017)\n if (blocked && this.escalationWebhook) {\n const apiBase = this.escalationWebhook.apiBaseUrl ?? 'http://localhost:3141';\n const expiresAt = approvalTokenExpires ?? (Date.now() + 3_600_000);\n const payload = {\n taskId: task.id,\n agentId,\n phase,\n policy: violations[0]?.policy ?? 'unknown',\n reason: violations[0]?.reason ?? 'Policy gate blocked execution',\n approvalEndpoint: `${apiBase}/api/tasks/${task.id}/approve`,\n approvalToken, // SIGNAL-017: include token so webhook receiver can approve\n expiresAt,\n timestamp: new Date().toISOString(),\n };\n fetch(this.escalationWebhook.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...this.escalationWebhook.headers },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(3000),\n }).catch((err) => {\n console.warn(`[OPAPolicyGate] Escalation webhook failed: ${err}`);\n });\n }\n\n return { passed, blocked, evaluations: evals, degraded, approvalToken, approvalTokenExpires };\n }\n\n /**\n * Derive minimum trust score based on task complexity + priority.\n * Mirrors the trust tier thresholds in security.ts.\n */\n private minimumScoreFor(task: Task): number {\n if (task.priority === 'critical') return 80;\n switch (task.complexity) {\n case 'epic': return 85;\n case 'complex': return 70;\n case 'moderate': return 60;\n default: return 50;\n }\n }\n}\n"]}
|