@fulcrum-governance/sdk 0.1.1 → 0.1.3

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +299 -6
  3. package/dist/__tests__/clients.test.d.ts +1 -0
  4. package/dist/__tests__/clients.test.js +847 -0
  5. package/dist/clients/agent.d.ts +70 -0
  6. package/dist/clients/agent.js +127 -0
  7. package/dist/clients/approval.d.ts +67 -0
  8. package/dist/clients/approval.js +103 -0
  9. package/dist/clients/budget.d.ts +221 -0
  10. package/dist/clients/budget.js +181 -0
  11. package/dist/clients/checkpoint.d.ts +191 -0
  12. package/dist/clients/checkpoint.js +195 -0
  13. package/dist/clients/envelope.d.ts +73 -0
  14. package/dist/clients/envelope.js +95 -0
  15. package/dist/clients/eventstore.d.ts +87 -0
  16. package/dist/clients/eventstore.js +113 -0
  17. package/dist/clients/index.d.ts +15 -0
  18. package/dist/clients/index.js +35 -0
  19. package/dist/clients/metrics.d.ts +126 -0
  20. package/dist/clients/metrics.js +162 -0
  21. package/dist/clients/policy.d.ts +83 -0
  22. package/dist/clients/policy.js +102 -0
  23. package/dist/clients/tenant.d.ts +66 -0
  24. package/dist/clients/tenant.js +92 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +25 -3
  27. package/dist/instrumentation/__tests__/autoGovern.test.d.ts +6 -0
  28. package/dist/instrumentation/__tests__/autoGovern.test.js +416 -0
  29. package/dist/instrumentation/__tests__/evaluator.test.d.ts +6 -0
  30. package/dist/instrumentation/__tests__/evaluator.test.js +712 -0
  31. package/dist/instrumentation/autoGovern.d.ts +57 -0
  32. package/dist/instrumentation/autoGovern.js +319 -0
  33. package/dist/instrumentation/evaluator.d.ts +50 -0
  34. package/dist/instrumentation/evaluator.js +218 -0
  35. package/dist/instrumentation/index.d.ts +28 -0
  36. package/dist/instrumentation/index.js +34 -0
  37. package/dist/instrumentation/types.d.ts +105 -0
  38. package/dist/instrumentation/types.js +20 -0
  39. package/package.json +4 -4
  40. package/proto/fulcrum/agent/v1/agent_service.proto +170 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Auto-governance activation for TypeScript/Node.js.
3
+ *
4
+ * This module provides the main entry point for activating Fulcrum governance.
5
+ * It patches Node.js builtins and framework decorators at runtime.
6
+ */
7
+ import type { InstrumentationConfig, Decision, Metrics } from './types';
8
+ import { PolicyEvaluator } from './evaluator';
9
+ /**
10
+ * Activate Fulcrum governance for TypeScript/Node.js.
11
+ *
12
+ * This function should be called BEFORE importing frameworks that use
13
+ * tools. It patches builtins and library exports.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { activate } from 'fulcrum/instrumentation';
18
+ * activate({ apiKey: 'flk_...', tenantId: 'my-tenant' });
19
+ *
20
+ * // Now import frameworks - they will be governed
21
+ * import fetch from 'node-fetch';
22
+ * ```
23
+ */
24
+ export declare function activate(userConfig?: InstrumentationConfig): void;
25
+ /**
26
+ * Deactivate Fulcrum governance.
27
+ *
28
+ * This stops the policy evaluator and removes patches where possible.
29
+ * Note: Some patches cannot be fully reverted.
30
+ */
31
+ export declare function deactivate(): void;
32
+ /**
33
+ * Check if Fulcrum governance is currently active.
34
+ */
35
+ export declare function isActive(): boolean;
36
+ /**
37
+ * Get the active policy evaluator (for internal use by patches).
38
+ */
39
+ export declare function getEvaluator(): PolicyEvaluator | null;
40
+ /**
41
+ * Get the active configuration (for internal use by patches).
42
+ */
43
+ export declare function getConfig(): Required<InstrumentationConfig> | null;
44
+ /**
45
+ * Record a metric value (for internal use by patches).
46
+ */
47
+ export declare function recordMetric(metric: keyof Metrics, increment?: number): void;
48
+ /**
49
+ * Get current metrics counters.
50
+ */
51
+ export declare function getMetrics(): Metrics;
52
+ /**
53
+ * Evaluate a tool call against policies.
54
+ *
55
+ * This is the main entry point for patches to check governance.
56
+ */
57
+ export declare function evaluateCall(toolName: string, actionType: string, resource?: string, toolArgs?: Record<string, unknown>, metadata?: Record<string, unknown>): Decision;
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ /**
3
+ * Auto-governance activation for TypeScript/Node.js.
4
+ *
5
+ * This module provides the main entry point for activating Fulcrum governance.
6
+ * It patches Node.js builtins and framework decorators at runtime.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.activate = activate;
10
+ exports.deactivate = deactivate;
11
+ exports.isActive = isActive;
12
+ exports.getEvaluator = getEvaluator;
13
+ exports.getConfig = getConfig;
14
+ exports.recordMetric = recordMetric;
15
+ exports.getMetrics = getMetrics;
16
+ exports.evaluateCall = evaluateCall;
17
+ const types_1 = require("./types");
18
+ const evaluator_1 = require("./evaluator");
19
+ // Global state
20
+ let evaluator = null;
21
+ let config = null;
22
+ let active = false;
23
+ const metrics = {
24
+ governedCalls: 0,
25
+ ungovernedCalls: 0,
26
+ deniedCalls: 0,
27
+ allowedCalls: 0,
28
+ patchFailures: 0,
29
+ };
30
+ /**
31
+ * Activate Fulcrum governance for TypeScript/Node.js.
32
+ *
33
+ * This function should be called BEFORE importing frameworks that use
34
+ * tools. It patches builtins and library exports.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { activate } from 'fulcrum/instrumentation';
39
+ * activate({ apiKey: 'flk_...', tenantId: 'my-tenant' });
40
+ *
41
+ * // Now import frameworks - they will be governed
42
+ * import fetch from 'node-fetch';
43
+ * ```
44
+ */
45
+ function activate(userConfig = {}) {
46
+ if (active) {
47
+ console.warn('[fulcrum] Governance already active');
48
+ return;
49
+ }
50
+ // Create configuration
51
+ config = { ...types_1.DEFAULT_CONFIG, ...userConfig };
52
+ // Log validation warnings
53
+ if (!config.apiKey) {
54
+ console.warn('[fulcrum] api_key not set - operating in offline mode');
55
+ }
56
+ if (!config.tenantId) {
57
+ console.warn('[fulcrum] tenant_id not set - required for policy evaluation');
58
+ }
59
+ // Create and start evaluator
60
+ evaluator = new evaluator_1.PolicyEvaluator(config);
61
+ evaluator.start();
62
+ // Apply patches
63
+ applyPatches();
64
+ active = true;
65
+ console.info(`[fulcrum] Governance activated (patches: ${[...config.enabledPatches].join(', ')})`);
66
+ }
67
+ /**
68
+ * Deactivate Fulcrum governance.
69
+ *
70
+ * This stops the policy evaluator and removes patches where possible.
71
+ * Note: Some patches cannot be fully reverted.
72
+ */
73
+ function deactivate() {
74
+ if (!active)
75
+ return;
76
+ // Stop evaluator
77
+ if (evaluator) {
78
+ evaluator.stop();
79
+ evaluator = null;
80
+ }
81
+ // Restore originals
82
+ restoreOriginals();
83
+ active = false;
84
+ config = null;
85
+ console.info('[fulcrum] Governance deactivated');
86
+ }
87
+ /**
88
+ * Check if Fulcrum governance is currently active.
89
+ */
90
+ function isActive() {
91
+ return active;
92
+ }
93
+ /**
94
+ * Get the active policy evaluator (for internal use by patches).
95
+ */
96
+ function getEvaluator() {
97
+ return evaluator;
98
+ }
99
+ /**
100
+ * Get the active configuration (for internal use by patches).
101
+ */
102
+ function getConfig() {
103
+ return config;
104
+ }
105
+ /**
106
+ * Record a metric value (for internal use by patches).
107
+ */
108
+ function recordMetric(metric, increment = 1) {
109
+ metrics[metric] += increment;
110
+ }
111
+ /**
112
+ * Get current metrics counters.
113
+ */
114
+ function getMetrics() {
115
+ return { ...metrics };
116
+ }
117
+ /**
118
+ * Evaluate a tool call against policies.
119
+ *
120
+ * This is the main entry point for patches to check governance.
121
+ */
122
+ function evaluateCall(toolName, actionType, resource, toolArgs = {}, metadata) {
123
+ if (!evaluator) {
124
+ recordMetric('ungovernedCalls');
125
+ return {
126
+ action: 'allow',
127
+ reason: 'Evaluator not initialized',
128
+ };
129
+ }
130
+ const request = {
131
+ toolName,
132
+ actionType,
133
+ resource,
134
+ toolArgs,
135
+ metadata,
136
+ };
137
+ const decision = evaluator.evaluate(request);
138
+ // Record metrics
139
+ recordMetric('governedCalls');
140
+ if (decision.action === 'allow') {
141
+ recordMetric('allowedCalls');
142
+ }
143
+ else if (decision.action === 'deny') {
144
+ recordMetric('deniedCalls');
145
+ }
146
+ return decision;
147
+ }
148
+ // Store original functions for restoration
149
+ const originals = new Map();
150
+ function applyPatches() {
151
+ if (!config)
152
+ return;
153
+ // Patch global fetch if enabled
154
+ if (config.enabledPatches.has('fetch') && typeof globalThis.fetch === 'function') {
155
+ patchFetch();
156
+ }
157
+ // Patch fs module if enabled
158
+ if (config.enabledPatches.has('fs')) {
159
+ patchFs();
160
+ }
161
+ // Patch child_process if enabled
162
+ if (config.enabledPatches.has('childProcess')) {
163
+ patchChildProcess();
164
+ }
165
+ }
166
+ function patchFetch() {
167
+ try {
168
+ const originalFetch = globalThis.fetch;
169
+ originals.set('fetch', originalFetch);
170
+ globalThis.fetch = async function governedFetch(input, init) {
171
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
172
+ const method = init?.method || 'GET';
173
+ const decision = evaluateCall('fetch', 'http_request', url, { method, url }, { headers: init?.headers });
174
+ if (decision.action === 'deny') {
175
+ throw new Error(`HTTP request to '${url}' blocked by Fulcrum policy: ${decision.reason}`);
176
+ }
177
+ return originalFetch(input, init);
178
+ };
179
+ console.debug('[fulcrum] Patched global fetch');
180
+ }
181
+ catch (err) {
182
+ console.warn(`[fulcrum] Failed to patch fetch: ${err instanceof Error ? err.message : err}`);
183
+ recordMetric('patchFailures');
184
+ }
185
+ }
186
+ function patchFs() {
187
+ try {
188
+ // Dynamic import to avoid issues if fs is not available
189
+ const fs = require('fs');
190
+ const originalReadFile = fs.readFile;
191
+ const originalWriteFile = fs.writeFile;
192
+ const originalReadFileSync = fs.readFileSync;
193
+ const originalWriteFileSync = fs.writeFileSync;
194
+ originals.set('fs.readFile', originalReadFile);
195
+ originals.set('fs.writeFile', originalWriteFile);
196
+ originals.set('fs.readFileSync', originalReadFileSync);
197
+ originals.set('fs.writeFileSync', originalWriteFileSync);
198
+ fs.readFile = function governedReadFile(path, ...args) {
199
+ const decision = evaluateCall('fs.readFile', 'file_read', String(path), { path });
200
+ if (decision.action === 'deny') {
201
+ const callback = args[args.length - 1];
202
+ if (typeof callback === 'function') {
203
+ callback(new Error(`File read '${path}' blocked by Fulcrum policy: ${decision.reason}`));
204
+ return;
205
+ }
206
+ throw new Error(`File read '${path}' blocked by Fulcrum policy: ${decision.reason}`);
207
+ }
208
+ return originalReadFile(path, ...args);
209
+ };
210
+ fs.writeFile = function governedWriteFile(path, data, ...args) {
211
+ const decision = evaluateCall('fs.writeFile', 'file_write', String(path), { path });
212
+ if (decision.action === 'deny') {
213
+ const callback = args[args.length - 1];
214
+ if (typeof callback === 'function') {
215
+ callback(new Error(`File write '${path}' blocked by Fulcrum policy: ${decision.reason}`));
216
+ return;
217
+ }
218
+ throw new Error(`File write '${path}' blocked by Fulcrum policy: ${decision.reason}`);
219
+ }
220
+ return originalWriteFile(path, data, ...args);
221
+ };
222
+ fs.readFileSync = function governedReadFileSync(path, options) {
223
+ const decision = evaluateCall('fs.readFileSync', 'file_read', String(path), { path });
224
+ if (decision.action === 'deny') {
225
+ throw new Error(`File read '${path}' blocked by Fulcrum policy: ${decision.reason}`);
226
+ }
227
+ return originalReadFileSync(path, options);
228
+ };
229
+ fs.writeFileSync = function governedWriteFileSync(path, data, options) {
230
+ const decision = evaluateCall('fs.writeFileSync', 'file_write', String(path), { path });
231
+ if (decision.action === 'deny') {
232
+ throw new Error(`File write '${path}' blocked by Fulcrum policy: ${decision.reason}`);
233
+ }
234
+ return originalWriteFileSync(path, data, options);
235
+ };
236
+ console.debug('[fulcrum] Patched fs module');
237
+ }
238
+ catch (err) {
239
+ console.warn(`[fulcrum] Failed to patch fs: ${err instanceof Error ? err.message : err}`);
240
+ recordMetric('patchFailures');
241
+ }
242
+ }
243
+ function patchChildProcess() {
244
+ try {
245
+ const childProcess = require('child_process');
246
+ const originalSpawn = childProcess.spawn;
247
+ const originalExec = childProcess.exec;
248
+ const originalExecSync = childProcess.execSync;
249
+ originals.set('child_process.spawn', originalSpawn);
250
+ originals.set('child_process.exec', originalExec);
251
+ originals.set('child_process.execSync', originalExecSync);
252
+ childProcess.spawn = function governedSpawn(command, args, options) {
253
+ const decision = evaluateCall('child_process.spawn', 'shell_execute', command, { command, args });
254
+ if (decision.action === 'deny') {
255
+ throw new Error(`Command '${command}' blocked by Fulcrum policy: ${decision.reason}`);
256
+ }
257
+ return originalSpawn(command, args, options);
258
+ };
259
+ childProcess.exec = function governedExec(command, ...args) {
260
+ const decision = evaluateCall('child_process.exec', 'shell_execute', command, { command });
261
+ if (decision.action === 'deny') {
262
+ const callback = args[args.length - 1];
263
+ if (typeof callback === 'function') {
264
+ callback(new Error(`Command '${command}' blocked by Fulcrum policy: ${decision.reason}`));
265
+ return;
266
+ }
267
+ throw new Error(`Command '${command}' blocked by Fulcrum policy: ${decision.reason}`);
268
+ }
269
+ return originalExec(command, ...args);
270
+ };
271
+ childProcess.execSync = function governedExecSync(command, options) {
272
+ const decision = evaluateCall('child_process.execSync', 'shell_execute', command, { command });
273
+ if (decision.action === 'deny') {
274
+ throw new Error(`Command '${command}' blocked by Fulcrum policy: ${decision.reason}`);
275
+ }
276
+ return originalExecSync(command, options);
277
+ };
278
+ console.debug('[fulcrum] Patched child_process module');
279
+ }
280
+ catch (err) {
281
+ console.warn(`[fulcrum] Failed to patch child_process: ${err instanceof Error ? err.message : err}`);
282
+ recordMetric('patchFailures');
283
+ }
284
+ }
285
+ function restoreOriginals() {
286
+ // Restore fetch
287
+ if (originals.has('fetch')) {
288
+ globalThis.fetch = originals.get('fetch');
289
+ }
290
+ // Restore fs
291
+ try {
292
+ const fs = require('fs');
293
+ if (originals.has('fs.readFile'))
294
+ fs.readFile = originals.get('fs.readFile');
295
+ if (originals.has('fs.writeFile'))
296
+ fs.writeFile = originals.get('fs.writeFile');
297
+ if (originals.has('fs.readFileSync'))
298
+ fs.readFileSync = originals.get('fs.readFileSync');
299
+ if (originals.has('fs.writeFileSync'))
300
+ fs.writeFileSync = originals.get('fs.writeFileSync');
301
+ }
302
+ catch {
303
+ // fs not available
304
+ }
305
+ // Restore child_process
306
+ try {
307
+ const childProcess = require('child_process');
308
+ if (originals.has('child_process.spawn'))
309
+ childProcess.spawn = originals.get('child_process.spawn');
310
+ if (originals.has('child_process.exec'))
311
+ childProcess.exec = originals.get('child_process.exec');
312
+ if (originals.has('child_process.execSync'))
313
+ childProcess.execSync = originals.get('child_process.execSync');
314
+ }
315
+ catch {
316
+ // child_process not available
317
+ }
318
+ originals.clear();
319
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Policy evaluator for SDK instrumentation.
3
+ *
4
+ * This module provides a lightweight policy evaluation client that:
5
+ * 1. Caches policies locally for fast evaluation
6
+ * 2. Syncs with fulcrum-server periodically
7
+ * 3. Fails open (allow) on errors to avoid crashing agents
8
+ */
9
+ import type { InstrumentationConfig, Decision, EvaluationRequest, Policy } from './types';
10
+ /**
11
+ * Lightweight policy evaluator for SDK use.
12
+ */
13
+ export declare class PolicyEvaluator {
14
+ private config;
15
+ private policies;
16
+ private lastSync;
17
+ private syncError;
18
+ private syncInterval;
19
+ private running;
20
+ constructor(config?: InstrumentationConfig);
21
+ /**
22
+ * Start the background policy sync.
23
+ */
24
+ start(): void;
25
+ /**
26
+ * Stop the background policy sync.
27
+ */
28
+ stop(): void;
29
+ /**
30
+ * Evaluate a request against cached policies.
31
+ *
32
+ * This method is designed to be fast (<1ms) and fail-safe.
33
+ * On any error, it returns ALLOW with a warning.
34
+ */
35
+ evaluate(request: EvaluationRequest): Decision;
36
+ private evaluateInternal;
37
+ private policyApplies;
38
+ private ruleMatches;
39
+ private conditionMatches;
40
+ private getFieldValue;
41
+ private getRuleAction;
42
+ private syncPolicies;
43
+ /**
44
+ * Manually update the policy cache (for testing).
45
+ */
46
+ updatePolicies(policies: Policy[]): void;
47
+ get policyCount(): number;
48
+ get lastSyncTime(): number;
49
+ get syncErrorMessage(): string | null;
50
+ }
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /**
3
+ * Policy evaluator for SDK instrumentation.
4
+ *
5
+ * This module provides a lightweight policy evaluation client that:
6
+ * 1. Caches policies locally for fast evaluation
7
+ * 2. Syncs with fulcrum-server periodically
8
+ * 3. Fails open (allow) on errors to avoid crashing agents
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.PolicyEvaluator = void 0;
12
+ const types_1 = require("./types");
13
+ /**
14
+ * Lightweight policy evaluator for SDK use.
15
+ */
16
+ class PolicyEvaluator {
17
+ constructor(config = {}) {
18
+ this.policies = [];
19
+ this.lastSync = 0;
20
+ this.syncError = null;
21
+ this.syncInterval = null;
22
+ this.running = false;
23
+ this.config = { ...types_1.DEFAULT_CONFIG, ...config };
24
+ }
25
+ /**
26
+ * Start the background policy sync.
27
+ */
28
+ start() {
29
+ if (this.running)
30
+ return;
31
+ this.running = true;
32
+ // Initial sync
33
+ this.syncPolicies().catch((err) => {
34
+ console.warn('[fulcrum] Initial policy sync failed:', err.message);
35
+ });
36
+ // Periodic sync
37
+ this.syncInterval = setInterval(() => this.syncPolicies().catch(() => { }), this.config.policySyncInterval * 1000);
38
+ console.info('[fulcrum] Policy evaluator started');
39
+ }
40
+ /**
41
+ * Stop the background policy sync.
42
+ */
43
+ stop() {
44
+ if (!this.running)
45
+ return;
46
+ this.running = false;
47
+ if (this.syncInterval) {
48
+ clearInterval(this.syncInterval);
49
+ this.syncInterval = null;
50
+ }
51
+ console.info('[fulcrum] Policy evaluator stopped');
52
+ }
53
+ /**
54
+ * Evaluate a request against cached policies.
55
+ *
56
+ * This method is designed to be fast (<1ms) and fail-safe.
57
+ * On any error, it returns ALLOW with a warning.
58
+ */
59
+ evaluate(request) {
60
+ try {
61
+ return this.evaluateInternal(request);
62
+ }
63
+ catch (err) {
64
+ console.warn(`[fulcrum] Policy evaluation error (failing open): ${err instanceof Error ? err.message : err}`);
65
+ return {
66
+ action: 'allow',
67
+ reason: `Evaluation error (fail-open): ${err instanceof Error ? err.message : err}`,
68
+ };
69
+ }
70
+ }
71
+ evaluateInternal(request) {
72
+ if (this.policies.length === 0) {
73
+ return {
74
+ action: 'allow',
75
+ reason: 'No policies loaded',
76
+ };
77
+ }
78
+ // Evaluate policies in priority order
79
+ for (const policy of this.policies) {
80
+ if (policy.status !== 'active')
81
+ continue;
82
+ if (!this.policyApplies(policy, request))
83
+ continue;
84
+ for (const rule of policy.rules) {
85
+ if (!rule.enabled)
86
+ continue;
87
+ if (this.ruleMatches(rule, request)) {
88
+ const action = this.getRuleAction(rule);
89
+ return {
90
+ action,
91
+ reason: rule.name || 'Matched rule',
92
+ matchedPolicyId: policy.policyId,
93
+ matchedRuleId: rule.ruleId,
94
+ };
95
+ }
96
+ }
97
+ }
98
+ return {
99
+ action: 'allow',
100
+ reason: 'No rules matched',
101
+ };
102
+ }
103
+ policyApplies(policy, request) {
104
+ const scope = policy.scope;
105
+ if (!scope)
106
+ return true;
107
+ if (scope.applyToAll)
108
+ return true;
109
+ if (scope.toolNames && scope.toolNames.length > 0) {
110
+ if (!scope.toolNames.includes(request.toolName)) {
111
+ return false;
112
+ }
113
+ }
114
+ return true;
115
+ }
116
+ ruleMatches(rule, request) {
117
+ if (rule.conditions.length === 0)
118
+ return true;
119
+ // All conditions must match (implicit AND)
120
+ return rule.conditions.every((condition) => this.conditionMatches(condition, request));
121
+ }
122
+ conditionMatches(condition, request) {
123
+ const fieldValue = this.getFieldValue(condition.field, request);
124
+ if (fieldValue === undefined)
125
+ return false;
126
+ const value = condition.value || '';
127
+ const values = condition.values || [];
128
+ switch (condition.operator) {
129
+ case 'equals':
130
+ return String(fieldValue) === value;
131
+ case 'not_equals':
132
+ return String(fieldValue) !== value;
133
+ case 'contains':
134
+ return String(fieldValue).includes(value);
135
+ case 'starts_with':
136
+ return String(fieldValue).startsWith(value);
137
+ case 'ends_with':
138
+ return String(fieldValue).endsWith(value);
139
+ case 'in':
140
+ return values.includes(String(fieldValue));
141
+ case 'not_in':
142
+ return !values.includes(String(fieldValue));
143
+ case 'matches':
144
+ try {
145
+ return new RegExp(value).test(String(fieldValue));
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ default:
151
+ console.warn(`[fulcrum] Unknown operator: ${condition.operator}`);
152
+ return false;
153
+ }
154
+ }
155
+ getFieldValue(field, request) {
156
+ if (field === 'tool_name')
157
+ return request.toolName;
158
+ if (field === 'action_type')
159
+ return request.actionType;
160
+ if (field === 'resource')
161
+ return request.resource;
162
+ if (field.startsWith('args.')) {
163
+ const argName = field.slice(5);
164
+ return request.toolArgs[argName];
165
+ }
166
+ if (field.startsWith('metadata.')) {
167
+ const metaName = field.slice(9);
168
+ return request.metadata?.[metaName];
169
+ }
170
+ return undefined;
171
+ }
172
+ getRuleAction(rule) {
173
+ if (rule.actions.length === 0)
174
+ return 'allow';
175
+ const actionType = rule.actions[0].actionType;
176
+ const actionMap = {
177
+ allow: 'allow',
178
+ deny: 'deny',
179
+ warn: 'warn',
180
+ escalate: 'escalate',
181
+ require_approval: 'require_approval',
182
+ };
183
+ return actionMap[actionType] || 'allow';
184
+ }
185
+ async syncPolicies() {
186
+ if (!this.config.apiKey || !this.config.tenantId) {
187
+ return;
188
+ }
189
+ // TODO: Implement actual HTTP/gRPC call to fulcrum-server
190
+ // For now, this is a no-op placeholder
191
+ //
192
+ // Example implementation:
193
+ // const response = await fetch(`${this.config.serverUrl}/api/v1/policies`, {
194
+ // headers: { Authorization: `Bearer ${this.config.apiKey}` },
195
+ // });
196
+ // const data = await response.json();
197
+ // this.policies = data.policies;
198
+ this.lastSync = Date.now();
199
+ this.syncError = null;
200
+ }
201
+ /**
202
+ * Manually update the policy cache (for testing).
203
+ */
204
+ updatePolicies(policies) {
205
+ this.policies = [...policies];
206
+ this.lastSync = Date.now();
207
+ }
208
+ get policyCount() {
209
+ return this.policies.length;
210
+ }
211
+ get lastSyncTime() {
212
+ return this.lastSync;
213
+ }
214
+ get syncErrorMessage() {
215
+ return this.syncError;
216
+ }
217
+ }
218
+ exports.PolicyEvaluator = PolicyEvaluator;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Fulcrum Auto-Instrumentation for TypeScript/Node.js.
3
+ *
4
+ * This module provides zero-code-change governance for AI agent tool calls.
5
+ * It patches Node.js builtins and framework decorators at runtime.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // IMPORTANT: activate BEFORE importing frameworks
10
+ * import { activate } from 'fulcrum/instrumentation';
11
+ * activate({ apiKey: 'flk_...', tenantId: 'tenant-123' });
12
+ *
13
+ * // Now import your frameworks - they will be automatically governed
14
+ * import { tool } from '@langchain/core/tools';
15
+ * import fetch from 'node-fetch';
16
+ * ```
17
+ *
18
+ * The instrumentation patches:
19
+ * - global fetch (file access)
20
+ * - node:fs (file operations)
21
+ * - child_process (shell commands)
22
+ * - node-fetch, axios (HTTP clients)
23
+ *
24
+ * All patches are fail-safe: governance errors never crash your agent.
25
+ */
26
+ export { activate, deactivate, isActive } from './autoGovern';
27
+ export { PolicyEvaluator } from './evaluator';
28
+ export type { InstrumentationConfig, Decision, EvaluationRequest, Action } from './types';