watchmyagents 1.4.6 → 1.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watchmyagents",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Security observability + real-time policy enforcement for AI agents. Local-first NDJSON capture with a continuous Watch daemon that auto-uploads anonymized signals, Shield CLI that blocks policy violations live (with policies pulled from Fortress cloud), anonymizer producing signals-only payloads, bidirectional sync with WatchMyAgents Fortress, and one-command install as an always-on launchd/systemd service — closing the recursive Watch→Guardian→Shield security loop.",
5
5
  "type": "module",
6
6
  "files": [
@@ -40,7 +40,7 @@ import {
40
40
  attachWmaWatchToAgent,
41
41
  adapterMeta,
42
42
  } from './sources/openai-agents-js.js';
43
- import { FortressPolicySource } from './shield/sources/fortress.js';
43
+ import { FortressPolicySource, postDecision } from './shield/sources/fortress.js';
44
44
  import { resolveFortressBase } from './fortress/url.js';
45
45
 
46
46
  /**
@@ -101,6 +101,7 @@ export function openaiAgents(options = {}) {
101
101
  // wma-shield gets its ruleset for Anthropic. Requires an agentId to scope
102
102
  // the pull, a base URL (option or WMA_FORTRESS_BASE_URL), and WMA_API_KEY.
103
103
  let fortressPolicySource = null;
104
+ let fortressDecisionSink = null;
104
105
  if (options.policies && options.policies.source === 'fortress') {
105
106
  if (!options.agentId) {
106
107
  throw new Error(
@@ -132,6 +133,14 @@ export function openaiAgents(options = {}) {
132
133
  requireSignedPolicies: options.policies.requireSignedPolicies,
133
134
  failMode: options.policies.failMode,
134
135
  });
136
+
137
+ // v1.4.7 (#1): ship decisions to the SAME Fortress (the control plane you
138
+ // pull policies from is where the decisions belong). Auto-on; opt out with
139
+ // policies.uploadDecisions === false to keep decisions local-only. The
140
+ // guardrail fires these best-effort, off its hot path.
141
+ if (options.policies.uploadDecisions !== false) {
142
+ fortressDecisionSink = (decision) => postDecision({ apiKey, base, decision });
143
+ }
135
144
  }
136
145
 
137
146
  // v1.4 Codex #2 — fail-loud refusal to start in enforce mode without
@@ -153,6 +162,8 @@ export function openaiAgents(options = {}) {
153
162
  const sharedOptions = {
154
163
  agentId: options.agentId, // v1.4.6: NDJSON path + native_agent_id
155
164
  fortressPolicySource, // v1.4.6: live Fortress ruleset (or null)
165
+ fortressDecisionSink, // v1.4.7 #1: ship decisions (or null)
166
+ signalsSalt: options.signalsSalt || process.env.WMA_SIGNALS_SALT, // hash session/input
156
167
  policiesPath: options.policiesPath,
157
168
  ruleset: options.ruleset,
158
169
  logDir: options.logDir,
@@ -54,6 +54,7 @@ import {
54
54
  import { evaluate, loadPolicies } from '../shield/policy.js';
55
55
  import { createContextTracker } from '../shield/context.js';
56
56
  import { DecisionLogger } from '../shield/decisions.js';
57
+ import { buildFortressDecisionPayload } from '../shield/upload.js';
57
58
  import { Logger } from '../logger.js';
58
59
  import { normalizeToolInput } from '../anonymizer.js';
59
60
 
@@ -632,6 +633,33 @@ export function wmaToolInputGuardrail(options = {}) {
632
633
  enforcementDelivered: enforcedBlock ? true : undefined,
633
634
  });
634
635
 
636
+ // 4b. v1.4.7 (#1): ship the decision to Fortress's ingest-decisions when
637
+ // a sink is configured. FIRE-AND-FORGET, off the guardrail's hot path —
638
+ // identical model to the Anthropic shield's fireToFortress(). The NDJSON
639
+ // row above is the durable local record; this live post is best-effort
640
+ // (a failure is swallowed, never blocks the tool call or crashes the
641
+ // guardrail). Payload is anonymized (hashes + decision/rule/provider) so
642
+ // Containment holds. Carries provider='openai-agents' + native_agent_id +
643
+ // enforcement_delivered (the v1.4.6 prereqs) so Fortress attributes it
644
+ // to the right runtime and surfaces degraded enforcement.
645
+ if (options.fortressDecisionSink) {
646
+ try {
647
+ const payload = buildFortressDecisionPayload({
648
+ agentId: loggerAgentId,
649
+ provider: PROVIDER,
650
+ nativeAgentId: loggerAgentId,
651
+ sessionId,
652
+ rawEvent: event,
653
+ normalized: event,
654
+ result,
655
+ decidedInMs,
656
+ signalsSalt: options.signalsSalt,
657
+ enforcementDelivered: enforcedBlock ? true : undefined,
658
+ });
659
+ Promise.resolve(options.fortressDecisionSink(payload)).catch(() => undefined);
660
+ } catch { /* payload build must never break enforcement */ }
661
+ }
662
+
635
663
  // 5. Watch log (the event itself, separate from the decision).
636
664
  await logger.write(event);
637
665