@vpdeva/blackwall-llm-shield-js 0.2.0 → 0.2.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.
package/README.md CHANGED
@@ -26,9 +26,10 @@ JavaScript security middleware for LLM applications in Node.js and Next.js. Blac
26
26
 
27
27
  ```bash
28
28
  npm install @vpdeva/blackwall-llm-shield-js
29
- npm install @xenova/transformers
30
29
  ```
31
30
 
31
+ The package now ships with local Transformers support wired as a first-class dependency, so teams do not need a second install step just to enable semantic scoring.
32
+
32
33
  ## Fast Start
33
34
 
34
35
  ```js
@@ -129,7 +130,7 @@ Use it before injecting retrieved documents into context so hostile instructions
129
130
 
130
131
  ### Contract Stability
131
132
 
132
- The 0.1.x line treats `guardModelRequest()`, `protectWithAdapter()`, `reviewModelResponse()`, `ToolPermissionFirewall`, and `RetrievalSanitizer` as the long-term integration contracts. The exported `CORE_INTERFACES` map can be logged or asserted by applications that want to pin expected behavior.
133
+ The 0.2.x line treats `guardModelRequest()`, `protectWithAdapter()`, `reviewModelResponse()`, `ToolPermissionFirewall`, and `RetrievalSanitizer` as the long-term integration contracts. The exported `CORE_INTERFACES` map can be logged or asserted by applications that want to pin expected behavior.
133
134
 
134
135
  Recommended presets:
135
136
 
@@ -141,6 +142,22 @@ Recommended presets:
141
142
  - `documentReview` for classification and document-review pipelines
142
143
  - `ragSearch` for search-heavy retrieval endpoints
143
144
  - `toolCalling` for routes that broker external actions
145
+ - `governmentStrict` for highly regulated public-sector and records-sensitive workflows
146
+ - `bankingPayments` for high-value payment and financial action routes
147
+ - `documentIntake` for upload-heavy intake and review flows
148
+ - `citizenServices` for identity-aware service delivery workflows
149
+ - `internalOpsAgent` for internal operational assistants with shadow-first defaults
150
+
151
+ ### Global Governance Pack
152
+
153
+ The 0.2.2 line also adds globally applicable enterprise controls that are useful across regulated industries, not just one country or sector:
154
+
155
+ - `DataClassificationGate` to classify traffic as `public`, `internal`, `confidential`, or `restricted`
156
+ - `ProviderRoutingPolicy` to keep sensitive classes on approved providers
157
+ - `ApprovalInboxModel` and `UploadQuarantineWorkflow` for quarantine and review-first intake
158
+ - `buildComplianceEventBundle()` and `sanitizeAuditEvent()` for audit-safe event export
159
+ - `RetrievalTrustScorer` and `OutboundCommunicationGuard` for retrieval trust and outbound checks
160
+ - `detectOperationalDrift()` for release-over-release noise monitoring
144
161
 
145
162
  ### `AuditTrail`
146
163
 
@@ -150,9 +167,10 @@ Use it to record signed events, summarize security activity, and power dashboard
150
167
 
151
168
  - `ValueAtRiskCircuitBreaker` for financial or high-value operational actions
152
169
  - `ShadowConsensusAuditor` for second-model or secondary-review logic conflict checks
170
+ - `CrossModelConsensusWrapper` for automatic cross-model verification of high-impact actions
153
171
  - `DigitalTwinOrchestrator` for mock tool environments and sandbox simulations
154
- - `suggestPolicyOverride()` for narrow false-positive tuning suggestions after HITL approvals
155
- - `AgentIdentityRegistry.issueSignedPassport()` for signed agent identity exchange
172
+ - `PolicyLearningLoop` plus `suggestPolicyOverride()` for narrow false-positive tuning suggestions after HITL approvals
173
+ - `AgentIdentityRegistry.issueSignedPassport()` and `issuePassportToken()` for signed agent identity exchange
156
174
 
157
175
  ## Example Workflows
158
176
 
@@ -243,6 +261,20 @@ const firewall = new ToolPermissionFirewall({
243
261
  });
244
262
  ```
245
263
 
264
+ ### Add automatic cross-model consensus
265
+
266
+ ```js
267
+ const consensus = new CrossModelConsensusWrapper({
268
+ auditorAdapter: geminiAuditorAdapter,
269
+ });
270
+
271
+ const firewall = new ToolPermissionFirewall({
272
+ allowedTools: ['issueRefund'],
273
+ crossModelConsensus: consensus,
274
+ consensusRequiredFor: ['issueRefund'],
275
+ });
276
+ ```
277
+
246
278
  ### Generate a digital twin for sandbox testing
247
279
 
248
280
  ```js
@@ -255,6 +287,8 @@ const twin = new DigitalTwinOrchestrator({
255
287
  await twin.simulateCall('lookupOrder', { orderId: 'ord_1' });
256
288
  ```
257
289
 
290
+ You can also derive a digital twin from `ToolPermissionFirewall` tool schemas with `DigitalTwinOrchestrator.fromToolPermissionFirewall(firewall)`.
291
+
258
292
  ### Protect a strict JSON workflow
259
293
 
260
294
  ```js
package/index.d.ts CHANGED
@@ -92,9 +92,53 @@ export class ShadowConsensusAuditor {
92
92
  inspect(input?: Record<string, unknown>): Record<string, unknown>;
93
93
  }
94
94
 
95
+ export class CrossModelConsensusWrapper {
96
+ constructor(options?: Record<string, unknown>);
97
+ evaluate(input?: Record<string, unknown>): Promise<Record<string, unknown>> | Record<string, unknown>;
98
+ }
99
+
95
100
  export class DigitalTwinOrchestrator {
96
101
  constructor(options?: Record<string, unknown>);
97
102
  generate(): Record<string, unknown>;
103
+ static fromToolPermissionFirewall(firewall: unknown): DigitalTwinOrchestrator;
104
+ }
105
+
106
+ export class DataClassificationGate {
107
+ constructor(options?: Record<string, unknown>);
108
+ classify(input?: Record<string, unknown>): string;
109
+ inspect(input?: Record<string, unknown>): Record<string, unknown>;
110
+ }
111
+
112
+ export class ProviderRoutingPolicy {
113
+ constructor(options?: Record<string, unknown>);
114
+ choose(input?: Record<string, unknown>): Record<string, unknown>;
115
+ }
116
+
117
+ export class ApprovalInboxModel {
118
+ constructor(options?: Record<string, unknown>);
119
+ createRequest(input?: Record<string, unknown>): Record<string, unknown>;
120
+ approve(id: string, approver: string): Record<string, unknown> | null;
121
+ summarize(): Record<string, unknown>;
122
+ }
123
+
124
+ export class RetrievalTrustScorer {
125
+ score(documents?: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
126
+ }
127
+
128
+ export class OutboundCommunicationGuard {
129
+ constructor(options?: Record<string, unknown>);
130
+ inspect(input?: Record<string, unknown>): Record<string, unknown>;
131
+ }
132
+
133
+ export class UploadQuarantineWorkflow {
134
+ constructor(options?: Record<string, unknown>);
135
+ inspectUpload(input?: Record<string, unknown>): Promise<Record<string, unknown>>;
136
+ }
137
+
138
+ export class PolicyLearningLoop {
139
+ constructor();
140
+ recordDecision(input?: Record<string, unknown>): Record<string, unknown> | null;
141
+ suggestOverrides(): Array<Record<string, unknown>>;
98
142
  }
99
143
 
100
144
  export class RetrievalSanitizer {
@@ -118,6 +162,9 @@ export function parseJsonOutput(output: unknown): unknown;
118
162
  export function normalizeIdentityMetadata(metadata?: Record<string, unknown>, resolver?: ((metadata: Record<string, unknown>) => Record<string, unknown> | null) | null): Record<string, unknown>;
119
163
  export function buildEnterpriseTelemetryEvent(event?: Record<string, unknown>, resolver?: ((metadata: Record<string, unknown>) => Record<string, unknown> | null) | null): Record<string, unknown>;
120
164
  export function buildPowerBIRecord(event?: Record<string, unknown>): Record<string, unknown>;
165
+ export function buildComplianceEventBundle(event?: Record<string, unknown>): Record<string, unknown>;
166
+ export function sanitizeAuditEvent(event?: Record<string, unknown>, options?: Record<string, unknown>): Record<string, unknown>;
167
+ export function detectOperationalDrift(previousSummary?: Record<string, unknown>, currentSummary?: Record<string, unknown>): Record<string, unknown>;
121
168
  export function suggestPolicyOverride(input?: Record<string, unknown>): Record<string, unknown> | null;
122
169
  export class PowerBIExporter {
123
170
  constructor(options?: Record<string, unknown>);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vpdeva/blackwall-llm-shield-js",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Open-source JavaScript enterprise LLM protection toolkit for Node.js and Next.js",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Vish <hello@vish.au> (https://vish.au)",
@@ -71,6 +71,9 @@
71
71
  "nextjs",
72
72
  "node"
73
73
  ],
74
+ "dependencies": {
75
+ "@xenova/transformers": "^2.17.2"
76
+ },
74
77
  "devDependencies": {
75
78
  "@changesets/cli": "^2.29.6"
76
79
  }
package/src/index.js CHANGED
@@ -173,6 +173,41 @@ const SHIELD_PRESETS = {
173
173
  shadowMode: false,
174
174
  policyPack: 'finance',
175
175
  },
176
+ governmentStrict: {
177
+ blockOnPromptInjection: true,
178
+ promptInjectionThreshold: 'medium',
179
+ notifyOnRiskLevel: 'medium',
180
+ shadowMode: false,
181
+ policyPack: 'government',
182
+ },
183
+ bankingPayments: {
184
+ blockOnPromptInjection: true,
185
+ promptInjectionThreshold: 'medium',
186
+ notifyOnRiskLevel: 'medium',
187
+ shadowMode: false,
188
+ policyPack: 'finance',
189
+ },
190
+ documentIntake: {
191
+ blockOnPromptInjection: true,
192
+ promptInjectionThreshold: 'high',
193
+ notifyOnRiskLevel: 'medium',
194
+ shadowMode: true,
195
+ policyPack: 'government',
196
+ },
197
+ citizenServices: {
198
+ blockOnPromptInjection: true,
199
+ promptInjectionThreshold: 'medium',
200
+ notifyOnRiskLevel: 'medium',
201
+ shadowMode: true,
202
+ policyPack: 'government',
203
+ },
204
+ internalOpsAgent: {
205
+ blockOnPromptInjection: true,
206
+ promptInjectionThreshold: 'medium',
207
+ notifyOnRiskLevel: 'medium',
208
+ shadowMode: true,
209
+ policyPack: 'finance',
210
+ },
176
211
  };
177
212
 
178
213
  const CORE_INTERFACE_VERSION = '1.0';
@@ -449,6 +484,173 @@ class PowerBIExporter {
449
484
  }
450
485
  }
451
486
 
487
+ class DataClassificationGate {
488
+ constructor(options = {}) {
489
+ this.defaultLevel = options.defaultLevel || 'internal';
490
+ this.providerAllowMap = options.providerAllowMap || {};
491
+ this.levelOrder = ['public', 'internal', 'confidential', 'restricted'];
492
+ }
493
+
494
+ classify({ metadata = {}, findings = [], messages = [] } = {}) {
495
+ if (metadata.classification) return metadata.classification;
496
+ const types = findings.map((item) => item.type).filter(Boolean);
497
+ if (types.some((type) => ['creditCard', 'tfn', 'passport', 'license', 'apiKey', 'jwt', 'bearerToken'].includes(type))) return 'restricted';
498
+ if (types.length) return 'confidential';
499
+ const text = JSON.stringify(messages || []).toLowerCase();
500
+ if (/\bconfidential|restricted|secret\b/.test(text)) return 'confidential';
501
+ return this.defaultLevel;
502
+ }
503
+
504
+ inspect({ metadata = {}, findings = [], messages = [], provider = null } = {}) {
505
+ const classification = this.classify({ metadata, findings, messages });
506
+ const allowedProviders = this.providerAllowMap[classification] || null;
507
+ const allowed = !provider || !allowedProviders || allowedProviders.includes(provider);
508
+ return {
509
+ allowed,
510
+ classification,
511
+ provider,
512
+ allowedProviders,
513
+ reason: allowed ? null : `Provider ${provider} is not allowed for ${classification} data`,
514
+ };
515
+ }
516
+ }
517
+
518
+ class ProviderRoutingPolicy {
519
+ constructor(options = {}) {
520
+ this.routes = options.routes || {};
521
+ this.fallbackProvider = options.fallbackProvider || null;
522
+ }
523
+
524
+ choose({ route = '', classification = 'internal', requestedProvider = null, candidates = [] } = {}) {
525
+ const routeConfig = this.routes[route] || this.routes.default || {};
526
+ const preferred = routeConfig[classification] || routeConfig.default || requestedProvider || this.fallbackProvider || candidates[0] || null;
527
+ const allowedCandidates = candidates.length ? candidates : [preferred].filter(Boolean);
528
+ const chosen = allowedCandidates.includes(preferred) ? preferred : allowedCandidates[0] || null;
529
+ return { provider: chosen, route, classification, requestedProvider, candidates: allowedCandidates };
530
+ }
531
+ }
532
+
533
+ class ApprovalInboxModel {
534
+ constructor(options = {}) {
535
+ this.requests = [];
536
+ this.requiredApprovers = options.requiredApprovers || 1;
537
+ }
538
+
539
+ createRequest(request = {}) {
540
+ const id = request.id || `apr_${crypto.randomBytes(6).toString('hex')}`;
541
+ const record = {
542
+ id,
543
+ status: 'pending',
544
+ requiredApprovers: request.requiredApprovers || this.requiredApprovers,
545
+ approvals: [],
546
+ createdAt: new Date().toISOString(),
547
+ ...request,
548
+ };
549
+ this.requests.push(record);
550
+ return record;
551
+ }
552
+
553
+ approve(id, approver) {
554
+ const request = this.requests.find((item) => item.id === id);
555
+ if (!request) return null;
556
+ if (!request.approvals.includes(approver)) request.approvals.push(approver);
557
+ request.status = request.approvals.length >= request.requiredApprovers ? 'approved' : 'pending';
558
+ return request;
559
+ }
560
+
561
+ summarize() {
562
+ return {
563
+ total: this.requests.length,
564
+ pending: this.requests.filter((item) => item.status === 'pending').length,
565
+ approved: this.requests.filter((item) => item.status === 'approved').length,
566
+ };
567
+ }
568
+ }
569
+
570
+ function buildComplianceEventBundle(event = {}) {
571
+ const payload = JSON.stringify(event);
572
+ const evidenceHash = crypto.createHash('sha256').update(payload).digest('hex');
573
+ return {
574
+ schemaVersion: '1.0',
575
+ generatedAt: new Date().toISOString(),
576
+ evidenceHash,
577
+ event,
578
+ };
579
+ }
580
+
581
+ function sanitizeAuditEvent(event = {}, options = {}) {
582
+ const keepEvidence = !!options.keepEvidence;
583
+ const clone = JSON.parse(JSON.stringify(event || {}));
584
+ if (!keepEvidence && clone.report && clone.report.sensitiveData) {
585
+ clone.report.sensitiveData.findings = (clone.report.sensitiveData.findings || []).map((finding) => ({ type: finding.type }));
586
+ }
587
+ return clone;
588
+ }
589
+
590
+ class RetrievalTrustScorer {
591
+ score(documents = []) {
592
+ return (documents || []).map((doc, index) => {
593
+ const metadata = doc && doc.metadata ? doc.metadata : {};
594
+ const sourceTrust = metadata.approved ? 0.4 : 0.1;
595
+ const freshness = metadata.fresh ? 0.3 : 0.1;
596
+ const origin = metadata.origin === 'trusted' ? 0.3 : 0.1;
597
+ const score = Number(Math.min(1, sourceTrust + freshness + origin).toFixed(2));
598
+ return {
599
+ id: doc && doc.id ? doc.id : `doc_${index + 1}`,
600
+ trustScore: score,
601
+ trusted: score >= 0.7,
602
+ metadata,
603
+ };
604
+ });
605
+ }
606
+ }
607
+
608
+ class OutboundCommunicationGuard {
609
+ constructor(options = {}) {
610
+ this.outputFirewall = options.outputFirewall || new OutputFirewall({ riskThreshold: 'high', enforceProfessionalTone: true });
611
+ }
612
+
613
+ inspect({ message, metadata = {} } = {}) {
614
+ const review = this.outputFirewall.inspect(message, {});
615
+ return {
616
+ allowed: review.allowed,
617
+ review,
618
+ channel: metadata.channel || 'outbound',
619
+ recipient: metadata.recipient || null,
620
+ };
621
+ }
622
+ }
623
+
624
+ class UploadQuarantineWorkflow {
625
+ constructor(options = {}) {
626
+ this.shield = options.shield || new BlackwallShield({ preset: 'documentIntake' });
627
+ this.inbox = options.approvalInbox || new ApprovalInboxModel({ requiredApprovers: 1 });
628
+ }
629
+
630
+ async inspectUpload({ content, metadata = {} } = {}) {
631
+ const guarded = await this.shield.guardModelRequest({
632
+ messages: [{ role: 'user', content }],
633
+ metadata: { ...metadata, feature: metadata.feature || 'upload_intake' },
634
+ });
635
+ const quarantined = !guarded.allowed || guarded.report.sensitiveData.hasSensitiveData;
636
+ const approvalRequest = quarantined ? this.inbox.createRequest({ route: metadata.route || '/uploads', reason: guarded.reason || 'Upload requires review', metadata }) : null;
637
+ return { quarantined, approvalRequest, guard: guarded };
638
+ }
639
+ }
640
+
641
+ function detectOperationalDrift(previousSummary = {}, currentSummary = {}) {
642
+ const previousBlocked = previousSummary.weeklyBlockEstimate || 0;
643
+ const currentBlocked = currentSummary.weeklyBlockEstimate || 0;
644
+ const delta = currentBlocked - previousBlocked;
645
+ return {
646
+ driftDetected: Math.abs(delta) > 0,
647
+ blockedDelta: delta,
648
+ previousBlocked,
649
+ currentBlocked,
650
+ severity: delta > 10 ? 'high' : delta > 0 ? 'medium' : 'low',
651
+ };
652
+ }
653
+
452
654
  function summarizeOperationalTelemetry(events = []) {
453
655
  const summary = {
454
656
  totalEvents: 0,
@@ -1719,6 +1921,25 @@ class AgentIdentityRegistry {
1719
1921
  blackwallProtected: !!unsigned.blackwallProtected,
1720
1922
  };
1721
1923
  }
1924
+
1925
+ issuePassportToken(agentId, options = {}) {
1926
+ const passport = this.issueSignedPassport(agentId, options);
1927
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
1928
+ const payload = Buffer.from(JSON.stringify(passport)).toString('base64url');
1929
+ const signature = crypto.createHmac('sha256', this.secret).update(`${header}.${payload}`).digest('base64url');
1930
+ return `${header}.${payload}.${signature}`;
1931
+ }
1932
+
1933
+ verifyPassportToken(token) {
1934
+ if (!token || typeof token !== 'string' || token.split('.').length !== 3) {
1935
+ return { valid: false, reason: 'Malformed passport token' };
1936
+ }
1937
+ const [header, payload, signature] = token.split('.');
1938
+ const expected = crypto.createHmac('sha256', this.secret).update(`${header}.${payload}`).digest('base64url');
1939
+ if (signature !== expected) return { valid: false, reason: 'Invalid passport token signature' };
1940
+ const passport = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
1941
+ return { valid: true, passport, ...this.verifySignedPassport(passport) };
1942
+ }
1722
1943
  }
1723
1944
 
1724
1945
  class ValueAtRiskCircuitBreaker {
@@ -1727,6 +1948,7 @@ class ValueAtRiskCircuitBreaker {
1727
1948
  this.windowMs = options.windowMs || (60 * 60 * 1000);
1728
1949
  this.revocationMs = options.revocationMs || (30 * 60 * 1000);
1729
1950
  this.valueExtractor = options.valueExtractor || ((args = {}, context = {}) => Number(context.actionValue != null ? context.actionValue : (args.amount != null ? args.amount : 0)));
1951
+ this.toolSchemas = options.toolSchemas || [];
1730
1952
  this.entries = [];
1731
1953
  this.revocations = new Map();
1732
1954
  }
@@ -1754,7 +1976,10 @@ class ValueAtRiskCircuitBreaker {
1754
1976
  };
1755
1977
  }
1756
1978
  this.entries = this.entries.filter((entry) => (now - entry.at) <= this.windowMs);
1757
- const actionValue = Math.max(0, Number(this.valueExtractor(args, context) || 0));
1979
+ const schema = this.toolSchemas.find((item) => item && item.name === tool) || {};
1980
+ const field = schema.monetaryValueField || schema.valueField || null;
1981
+ const schemaValue = field ? Number(args && args[field]) : 0;
1982
+ const actionValue = Math.max(0, Number((schemaValue || this.valueExtractor(args, context)) || 0));
1758
1983
  const key = sessionId || context.agentId || context.agent_id || context.userId || context.user_id || 'default';
1759
1984
  const relevant = this.entries.filter((entry) => entry.key === key);
1760
1985
  const riskWindowValue = relevant.reduce((sum, entry) => sum + entry.value, 0) + actionValue;
@@ -1809,6 +2034,40 @@ class ShadowConsensusAuditor {
1809
2034
  }
1810
2035
  }
1811
2036
 
2037
+ class CrossModelConsensusWrapper {
2038
+ constructor(options = {}) {
2039
+ this.primaryAdapter = options.primaryAdapter || null;
2040
+ this.auditorAdapter = options.auditorAdapter || null;
2041
+ this.decisionParser = options.decisionParser || ((output) => {
2042
+ const text = typeof output === 'string' ? output : JSON.stringify(output || '');
2043
+ if (/\b(block|unsafe|deny|disagree)\b/i.test(text)) return 'block';
2044
+ return 'allow';
2045
+ });
2046
+ }
2047
+
2048
+ async evaluate({ messages = [], metadata = {}, primaryResult = null } = {}) {
2049
+ if (!this.auditorAdapter || typeof this.auditorAdapter.invoke !== 'function') {
2050
+ return { agreed: true, disagreement: false, reason: null, primaryDecision: 'allow', auditorDecision: 'allow' };
2051
+ }
2052
+ const primaryDecision = primaryResult && primaryResult.blocked ? 'block' : 'allow';
2053
+ const response = await this.auditorAdapter.invoke({ messages, metadata, primaryResult });
2054
+ const output = typeof this.auditorAdapter.extractOutput === 'function'
2055
+ ? this.auditorAdapter.extractOutput(response && Object.prototype.hasOwnProperty.call(response, 'response') ? response.response : response)
2056
+ : (response && response.output) || response;
2057
+ const auditorDecision = this.decisionParser(output);
2058
+ const disagreement = auditorDecision !== primaryDecision;
2059
+ return {
2060
+ agreed: !disagreement,
2061
+ disagreement,
2062
+ primaryDecision,
2063
+ auditorDecision,
2064
+ reason: disagreement ? 'Logic Conflict: cross-model auditor disagreed with the primary decision' : null,
2065
+ auditorResponse: response,
2066
+ auditorOutput: output,
2067
+ };
2068
+ }
2069
+ }
2070
+
1812
2071
  class DigitalTwinOrchestrator {
1813
2072
  constructor(options = {}) {
1814
2073
  this.toolSchemas = options.toolSchemas || [];
@@ -1834,6 +2093,13 @@ class DigitalTwinOrchestrator {
1834
2093
  invocations: this.invocations,
1835
2094
  };
1836
2095
  }
2096
+
2097
+ static fromToolPermissionFirewall(firewall) {
2098
+ const schemas = Array.isArray(firewall && firewall.options && firewall.options.toolSchemas)
2099
+ ? firewall.options.toolSchemas
2100
+ : [];
2101
+ return new DigitalTwinOrchestrator({ toolSchemas: schemas });
2102
+ }
1837
2103
  }
1838
2104
 
1839
2105
  function suggestPolicyOverride({ route = null, approval = null, guardResult = null, toolDecision = null } = {}) {
@@ -1861,6 +2127,21 @@ function suggestPolicyOverride({ route = null, approval = null, guardResult = nu
1861
2127
  return null;
1862
2128
  }
1863
2129
 
2130
+ class PolicyLearningLoop {
2131
+ constructor() {
2132
+ this.decisions = [];
2133
+ }
2134
+
2135
+ recordDecision(input = {}) {
2136
+ this.decisions.push({ ...input, recordedAt: new Date().toISOString() });
2137
+ return suggestPolicyOverride(input);
2138
+ }
2139
+
2140
+ suggestOverrides() {
2141
+ return this.decisions.map((entry) => suggestPolicyOverride(entry)).filter(Boolean);
2142
+ }
2143
+ }
2144
+
1864
2145
  class AgenticCapabilityGater {
1865
2146
  constructor(options = {}) {
1866
2147
  this.registry = options.registry || new AgentIdentityRegistry();
@@ -1999,10 +2280,12 @@ class ToolPermissionFirewall {
1999
2280
  allowedTools: [],
2000
2281
  blockedTools: [],
2001
2282
  validators: {},
2283
+ toolSchemas: [],
2002
2284
  requireHumanApprovalFor: [],
2003
2285
  capabilityGater: null,
2004
2286
  valueAtRiskCircuitBreaker: null,
2005
2287
  consensusAuditor: null,
2288
+ crossModelConsensus: null,
2006
2289
  consensusRequiredFor: [],
2007
2290
  onApprovalRequest: null,
2008
2291
  approvalWebhookUrl: null,
@@ -2064,6 +2347,15 @@ class ToolPermissionFirewall {
2064
2347
  };
2065
2348
  }
2066
2349
  }
2350
+ if (this.options.crossModelConsensus && (context.highImpact || this.options.consensusRequiredFor.includes(tool))) {
2351
+ return {
2352
+ allowed: false,
2353
+ reason: 'Cross-model consensus requires async inspection',
2354
+ requiresApproval: true,
2355
+ requiresAsyncConsensus: true,
2356
+ approvalRequest: { tool, args, context },
2357
+ };
2358
+ }
2067
2359
  const requiresApproval = this.options.requireHumanApprovalFor.includes(tool);
2068
2360
  return {
2069
2361
  allowed: !requiresApproval,
@@ -2075,6 +2367,24 @@ class ToolPermissionFirewall {
2075
2367
 
2076
2368
  async inspectCallAsync(input = {}) {
2077
2369
  const result = this.inspectCall(input);
2370
+ if (result.requiresAsyncConsensus && this.options.crossModelConsensus) {
2371
+ const consensus = await this.options.crossModelConsensus.evaluate({
2372
+ messages: input.context && input.context.consensusMessages ? input.context.consensusMessages : [{ role: 'user', content: JSON.stringify({ tool: input.tool, args: input.args, context: input.context }) }],
2373
+ metadata: input.context || {},
2374
+ primaryResult: result,
2375
+ });
2376
+ if (consensus.disagreement) {
2377
+ return {
2378
+ allowed: false,
2379
+ reason: consensus.reason,
2380
+ requiresApproval: true,
2381
+ logicConflict: true,
2382
+ consensus,
2383
+ approvalRequest: { tool: input.tool, args: input.args || {}, context: input.context || {}, consensus },
2384
+ };
2385
+ }
2386
+ return { allowed: true, reason: null, requiresApproval: false, consensus };
2387
+ }
2078
2388
  if (result.requiresApproval) {
2079
2389
  if (typeof this.options.onApprovalRequest === 'function') {
2080
2390
  await this.options.onApprovalRequest(result.approvalRequest);
@@ -2431,12 +2741,14 @@ module.exports = {
2431
2741
  AuditTrail,
2432
2742
  BlackwallShield,
2433
2743
  CoTScanner,
2744
+ CrossModelConsensusWrapper,
2434
2745
  DigitalTwinOrchestrator,
2435
2746
  ImageMetadataScanner,
2436
2747
  LightweightIntentScorer,
2437
2748
  MCPSecurityProxy,
2438
2749
  OutputFirewall,
2439
2750
  PowerBIExporter,
2751
+ PolicyLearningLoop,
2440
2752
  RetrievalSanitizer,
2441
2753
  SessionBuffer,
2442
2754
  ShadowConsensusAuditor,
@@ -2476,6 +2788,15 @@ module.exports = {
2476
2788
  normalizeIdentityMetadata,
2477
2789
  buildEnterpriseTelemetryEvent,
2478
2790
  buildPowerBIRecord,
2791
+ buildComplianceEventBundle,
2792
+ sanitizeAuditEvent,
2793
+ detectOperationalDrift,
2794
+ DataClassificationGate,
2795
+ ProviderRoutingPolicy,
2796
+ ApprovalInboxModel,
2797
+ RetrievalTrustScorer,
2798
+ OutboundCommunicationGuard,
2799
+ UploadQuarantineWorkflow,
2479
2800
  parseJsonOutput,
2480
2801
  createOpenAIAdapter,
2481
2802
  createAnthropicAdapter,