@grc-claw/compliance-orchestrator 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,387 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type {
3
+ FrameworkCode,
4
+ RegulationAST,
5
+ ASTControlNode,
6
+ CrosswalkEntry,
7
+ ComplianceState,
8
+ ControlStatus,
9
+ DriftEvent,
10
+ RiskAssessment,
11
+ EvidenceChain,
12
+ CollectedEvidence,
13
+ } from '../types.js';
14
+
15
+ export interface GraphNode {
16
+ id: string;
17
+ type: 'framework' | 'control' | 'evidence' | 'agent' | 'infrastructure' | 'risk' | 'org';
18
+ label: string;
19
+ properties: Record<string, unknown>;
20
+ framework?: FrameworkCode;
21
+ }
22
+
23
+ export interface GraphEdge {
24
+ source: string;
25
+ target: string;
26
+ relationship: string;
27
+ weight: number;
28
+ properties: Record<string, unknown>;
29
+ }
30
+
31
+ export interface AttackPath {
32
+ nodes: string[];
33
+ edges: string[];
34
+ riskScore: number;
35
+ description: string;
36
+ }
37
+
38
+ export interface BlastRadius {
39
+ controlId: string;
40
+ impactScore: number;
41
+ affectedControls: string[];
42
+ affectedSystems: string[];
43
+ propagationDepth: number;
44
+ }
45
+
46
+ export interface CompliancePosture {
47
+ orgId: string;
48
+ timestamp: string;
49
+ overallScore: number;
50
+ frameworkScores: Map<FrameworkCode, number>;
51
+ controlCompliance: Map<string, boolean>;
52
+ riskHeatmap: RiskHeatmapEntry[];
53
+ recommendations: Recommendation[];
54
+ }
55
+
56
+ export interface RiskHeatmapEntry {
57
+ controlFamily: string;
58
+ severity: string;
59
+ count: number;
60
+ riskScore: number;
61
+ }
62
+
63
+ export interface Recommendation {
64
+ id: string;
65
+ priority: 'critical' | 'high' | 'medium' | 'low';
66
+ controlId: string;
67
+ title: string;
68
+ description: string;
69
+ estimatedImpact: number;
70
+ estimatedEffort: string;
71
+ }
72
+
73
+ export class UnifiedComplianceGraph {
74
+ private nodes: Map<string, GraphNode> = new Map();
75
+ private edges: GraphEdge[] = [];
76
+ private adjacencyList: Map<string, GraphEdge[]> = new Map();
77
+
78
+ constructor(asts: RegulationAST[]) {
79
+ for (const ast of asts) {
80
+ this.addFrameworkNode(ast);
81
+ for (const control of ast.controls) {
82
+ this.addControlNode(control, ast.framework);
83
+ }
84
+ }
85
+ }
86
+
87
+ private addFrameworkNode(ast: RegulationAST): void {
88
+ this.nodes.set(`framework:${ast.framework}`, {
89
+ id: `framework:${ast.framework}`,
90
+ type: 'framework',
91
+ label: ast.metadata.title,
92
+ properties: {
93
+ version: ast.version,
94
+ issuer: ast.metadata.issuer,
95
+ totalControls: ast.metadata.totalControls,
96
+ families: ast.metadata.families,
97
+ },
98
+ framework: ast.framework,
99
+ });
100
+ }
101
+
102
+ private addControlNode(control: ASTControlNode, framework: FrameworkCode): void {
103
+ const nodeId = `control:${framework}:${control.code}`;
104
+ this.nodes.set(nodeId, {
105
+ id: nodeId,
106
+ type: 'control',
107
+ label: `${control.code} - ${control.title}`,
108
+ properties: {
109
+ id: control.id,
110
+ code: control.code,
111
+ title: control.title,
112
+ crossRefs: control.crossRefs,
113
+ severity: control.severity,
114
+ },
115
+ framework,
116
+ });
117
+
118
+ this.edges.push({
119
+ source: `framework:${framework}`,
120
+ target: nodeId,
121
+ relationship: 'contains',
122
+ weight: 1,
123
+ properties: {},
124
+ });
125
+ }
126
+
127
+ addCrosswalks(crosswalks: CrosswalkEntry[]): void {
128
+ for (const cw of crosswalks) {
129
+ const sourceId = `control:${cw.sourceFramework}:${cw.sourceControl}`;
130
+ const targetId = `control:${cw.targetFramework}:${cw.targetControl}`;
131
+
132
+ if (this.nodes.has(sourceId) && this.nodes.has(targetId)) {
133
+ this.edges.push({
134
+ source: sourceId,
135
+ target: targetId,
136
+ relationship: cw.relationship,
137
+ weight: cw.confidence,
138
+ properties: { confidence: cw.confidence },
139
+ });
140
+ }
141
+ }
142
+ }
143
+
144
+ addEvidenceNode(evidence: CollectedEvidence, controlId: string): void {
145
+ const nodeId = `evidence:${evidence.id}`;
146
+ this.nodes.set(nodeId, {
147
+ id: nodeId,
148
+ type: 'evidence',
149
+ label: `${evidence.type} for ${controlId}`,
150
+ properties: {
151
+ hash: evidence.hash,
152
+ timestamp: evidence.timestamp,
153
+ valid: evidence.valid,
154
+ source: evidence.source,
155
+ },
156
+ });
157
+
158
+ this.edges.push({
159
+ source: nodeId,
160
+ target: `control:${controlId}`,
161
+ relationship: 'supports',
162
+ weight: evidence.valid ? 1 : 0,
163
+ properties: { valid: evidence.valid },
164
+ });
165
+ }
166
+
167
+ addAgentNode(agentId: string, role: string, tools: string[]): void {
168
+ const nodeId = `agent:${agentId}`;
169
+ this.nodes.set(nodeId, {
170
+ id: nodeId,
171
+ type: 'agent',
172
+ label: `Agent ${agentId}`,
173
+ properties: { role, tools },
174
+ });
175
+ }
176
+
177
+ addInfrastructureNode(systemId: string, systemType: string, controls: string[]): void {
178
+ const nodeId = `infra:${systemId}`;
179
+ this.nodes.set(nodeId, {
180
+ id: nodeId,
181
+ type: 'infrastructure',
182
+ label: systemId,
183
+ properties: { systemType, controls },
184
+ });
185
+
186
+ for (const control of controls) {
187
+ this.edges.push({
188
+ source: nodeId,
189
+ target: `control:${control}`,
190
+ relationship: 'implements',
191
+ weight: 0.8,
192
+ properties: {},
193
+ });
194
+ }
195
+ }
196
+
197
+ traceAttackPaths(startNodeId: string, maxDepth: number = 5): AttackPath[] {
198
+ const paths: AttackPath[] = [];
199
+ const visited = new Set<string>();
200
+ const queue: { nodeId: string; path: string[]; edges: string[] }[] = [];
201
+
202
+ queue.push({ nodeId: startNodeId, path: [startNodeId], edges: [] });
203
+ visited.add(startNodeId);
204
+
205
+ while (queue.length > 0 && paths.length < 10) {
206
+ const current = queue.shift()!;
207
+ const neighbors = this.adjacencyList.get(current.nodeId) ?? [];
208
+
209
+ for (const edge of neighbors) {
210
+ const targetId = edge.source === current.nodeId ? edge.target : edge.source;
211
+ if (visited.has(targetId)) continue;
212
+ visited.add(targetId);
213
+
214
+ const newPath = [...current.path, targetId];
215
+ const newEdges = [...current.edges, edge.relationship];
216
+
217
+ if (newPath.length > 1) {
218
+ paths.push({
219
+ nodes: newPath,
220
+ edges: newEdges,
221
+ riskScore: this.calculatePathRisk(newPath),
222
+ description: this.describeAttackPath(newPath),
223
+ });
224
+ }
225
+
226
+ if (newPath.length < maxDepth) {
227
+ queue.push({ nodeId: targetId, path: newPath, edges: newEdges });
228
+ }
229
+ }
230
+ }
231
+
232
+ return paths.sort((a, b) => b.riskScore - a.riskScore);
233
+ }
234
+
235
+ calculateBlastRadius(controlId: string): BlastRadius {
236
+ const affectedControls: string[] = [];
237
+ const affectedSystems: string[] = [];
238
+ let propagationDepth = 0;
239
+
240
+ const bfs = (startId: string, depth: number = 0): void => {
241
+ if (depth > 3) return;
242
+ const outgoing = this.edges.filter((e) => e.source === startId && e.relationship !== 'contains');
243
+
244
+ for (const edge of outgoing) {
245
+ const targetNode = this.nodes.get(edge.target);
246
+ if (!targetNode) continue;
247
+
248
+ if (targetNode.type === 'control') {
249
+ affectedControls.push(edge.target);
250
+ } else if (targetNode.type === 'infrastructure') {
251
+ affectedSystems.push(edge.target);
252
+ }
253
+
254
+ propagationDepth = Math.max(propagationDepth, depth + 1);
255
+ bfs(edge.target, depth + 1);
256
+ }
257
+ };
258
+
259
+ bfs(controlId);
260
+
261
+ const impactScore = Math.min(
262
+ (affectedControls.length * 0.3 + affectedSystems.length * 0.5 + propagationDepth * 0.2),
263
+ 1
264
+ );
265
+
266
+ return {
267
+ controlId,
268
+ impactScore,
269
+ affectedControls: [...new Set(affectedControls)],
270
+ affectedSystems: [...new Set(affectedSystems)],
271
+ propagationDepth,
272
+ };
273
+ }
274
+
275
+ assessAgentRisk(agentId: string, context: { tool: string; args: Record<string, unknown> }): number {
276
+ const agentNode = this.nodes.get(`agent:${agentId}`);
277
+ if (!agentNode) return 1.0;
278
+
279
+ const connectedControls = this.edges
280
+ .filter((e) => e.source === `agent:${agentId}`)
281
+ .map((e) => this.nodes.get(e.target))
282
+ .filter((n) => n?.type === 'control');
283
+
284
+ const vulnerableControls = connectedControls.filter((n) => {
285
+ const evidence = this.edges.filter((e) => e.target === n?.id && e.relationship === 'supports');
286
+ return evidence.length === 0;
287
+ });
288
+
289
+ return vulnerableControls.length / Math.max(connectedControls.length, 1);
290
+ }
291
+
292
+ calculateCompliancePosture(orgId: string, states: ComplianceState[]): CompliancePosture {
293
+ const frameworkScores = new Map<FrameworkCode, number>();
294
+ const controlCompliance = new Map<string, boolean>();
295
+ const riskHeatmap: RiskHeatmapEntry[] = [];
296
+ const recommendations: Recommendation[] = [];
297
+
298
+ for (const state of states) {
299
+ const compliant = state.controlStatuses.filter((s) => s.status === 'compliant').length;
300
+ const total = state.controlStatuses.length;
301
+ const score = total > 0 ? (compliant / total) * 100 : 0;
302
+ frameworkScores.set(state.framework, score);
303
+
304
+ for (const cs of state.controlStatuses) {
305
+ controlCompliance.set(cs.controlId, cs.status === 'compliant');
306
+
307
+ if (cs.status !== 'compliant' && cs.issues.length > 0) {
308
+ recommendations.push({
309
+ id: `rec-${cs.controlId}-${Date.now()}`,
310
+ priority: cs.issues[0].severity === 'CRITICAL' ? 'critical' : 'high',
311
+ controlId: cs.controlId,
312
+ title: `Remediate ${cs.controlId}`,
313
+ description: cs.issues[0].description,
314
+ estimatedImpact: cs.score,
315
+ estimatedEffort: '1-4 hours',
316
+ });
317
+ }
318
+ }
319
+
320
+ for (const risk of state.risks) {
321
+ const existing = riskHeatmap.find(
322
+ (r) => r.severity === (risk.riskScore > 0.7 ? 'CRITICAL' : risk.riskScore > 0.4 ? 'HIGH' : 'MEDIUM')
323
+ );
324
+ if (existing) {
325
+ existing.count++;
326
+ existing.riskScore += risk.riskScore;
327
+ } else {
328
+ riskHeatmap.push({
329
+ controlFamily: risk.controlId.split('-')[0],
330
+ severity: risk.riskScore > 0.7 ? 'CRITICAL' : risk.riskScore > 0.4 ? 'HIGH' : 'MEDIUM',
331
+ count: 1,
332
+ riskScore: risk.riskScore,
333
+ });
334
+ }
335
+ }
336
+ }
337
+
338
+ const overallScore = Array.from(frameworkScores.values()).reduce((a, b) => a + b, 0) / Math.max(frameworkScores.size, 1);
339
+
340
+ return {
341
+ orgId,
342
+ timestamp: new Date().toISOString(),
343
+ overallScore,
344
+ frameworkScores,
345
+ controlCompliance,
346
+ riskHeatmap,
347
+ recommendations: recommendations.sort((a, b) => {
348
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
349
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
350
+ }),
351
+ };
352
+ }
353
+
354
+ private calculatePathRisk(path: string[]): number {
355
+ let risk = 0;
356
+ for (const nodeId of path) {
357
+ const node = this.nodes.get(nodeId);
358
+ if (node?.type === 'control') {
359
+ const severity = (node.properties.severity as string) ?? 'MEDIUM';
360
+ const severityScore = { LOW: 0.2, MEDIUM: 0.5, HIGH: 0.8, CRITICAL: 1.0 };
361
+ risk += severityScore[severity as keyof typeof severityScore] ?? 0.5;
362
+ }
363
+ }
364
+ return Math.min(risk / path.length, 1);
365
+ }
366
+
367
+ private describeAttackPath(path: string[]): string {
368
+ const descriptions = path.map((nodeId) => {
369
+ const node = this.nodes.get(nodeId);
370
+ if (!node) return nodeId;
371
+ return `${node.type}(${node.label})`;
372
+ });
373
+ return descriptions.join(' → ');
374
+ }
375
+
376
+ getGraphHash(): string {
377
+ const content = JSON.stringify({
378
+ nodeCount: this.nodes.size,
379
+ edgeCount: this.edges.length,
380
+ nodeTypes: Array.from(this.nodes.values()).reduce((acc, n) => {
381
+ acc[n.type] = (acc[n.type] ?? 0) + 1;
382
+ return acc;
383
+ }, {} as Record<string, number>),
384
+ });
385
+ return createHash('sha256').update(content).digest('hex');
386
+ }
387
+ }
package/src/index.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { RegulationASTCompiler } from './compiler/RegulationASTCompiler.js';
2
+ import { NeuroSymbolicReasoner } from './reasoner/NeuroSymbolicReasoner.js';
3
+ import { UnifiedComplianceGraph } from './graph/UnifiedComplianceGraph.js';
4
+ import type {
5
+ FrameworkCode,
6
+ ComplianceState,
7
+ CompliancePlan,
8
+ ComplianceAudit,
9
+ RegulationAST,
10
+ ControlStatus,
11
+ DriftEvent,
12
+ RiskAssessment,
13
+ } from './types.js';
14
+
15
+ export * from './types.js';
16
+ export { RegulationASTCompiler, getEvidenceDeduplicationMap } from './compiler/RegulationASTCompiler.js';
17
+ export { NeuroSymbolicReasoner } from './reasoner/NeuroSymbolicReasoner.js';
18
+ export { UnifiedComplianceGraph } from './graph/UnifiedComplianceGraph.js';
19
+ export type { ReasoningResult, ReasoningContext, ReasoningContext as NeuroSymbolicContext } from './reasoner/NeuroSymbolicReasoner.js';
20
+ export type { GraphNode, GraphEdge, AttackPath, BlastRadius, CompliancePosture, Recommendation } from './graph/UnifiedComplianceGraph.js';
21
+
22
+ export interface ComplianceOrchestratorConfig {
23
+ orgId: string;
24
+ enabledFrameworks: FrameworkCode[];
25
+ riskTolerance: 'low' | 'medium' | 'high';
26
+ autoRemediate: boolean;
27
+ continuousScanInterval: number;
28
+ }
29
+
30
+ export interface ContinuousComplianceResult {
31
+ orgId: string;
32
+ timestamp: string;
33
+ states: ComplianceState[];
34
+ drift: DriftEvent[];
35
+ risks: RiskAssessment[];
36
+ graphHash: string;
37
+ overallScore: number;
38
+ }
39
+
40
+ export class ComplianceSuperOrchestrator {
41
+ private compiler: RegulationASTCompiler;
42
+ private reasoner: NeuroSymbolicReasoner;
43
+ private graph: UnifiedComplianceGraph;
44
+ private config: ComplianceOrchestratorConfig;
45
+ private states: Map<FrameworkCode, ComplianceState> = new Map();
46
+
47
+ constructor(config: ComplianceOrchestratorConfig) {
48
+ this.config = config;
49
+ this.compiler = new RegulationASTCompiler();
50
+ const asts = this.compiler.getAllASTs();
51
+ this.reasoner = new NeuroSymbolicReasoner(new Map(asts.map((a) => [a.framework, a])));
52
+ this.graph = new UnifiedComplianceGraph(asts);
53
+ }
54
+
55
+ async continuousComplianceLoop(
56
+ reasoningContexts: Map<FrameworkCode, import('./reasoner/NeuroSymbolicReasoner.js').ReasoningContext>
57
+ ): Promise<ContinuousComplianceResult> {
58
+ const states: ComplianceState[] = [];
59
+ const allDrift: DriftEvent[] = [];
60
+ const allRisks: RiskAssessment[] = [];
61
+
62
+ for (const [framework, context] of reasoningContexts) {
63
+ const previousState = this.states.get(framework);
64
+ const enrichedContext = { ...context, previousState };
65
+ const state = await this.reasoner.reason(enrichedContext);
66
+ states.push(state);
67
+ allDrift.push(...state.drift);
68
+ allRisks.push(...state.risks);
69
+ this.states.set(framework, state);
70
+ }
71
+
72
+ const compliant = states.reduce(
73
+ (acc, s) => acc + s.controlStatuses.filter((c) => c.status === 'compliant').length,
74
+ 0
75
+ );
76
+ const total = states.reduce((acc, s) => acc + s.controlStatuses.length, 0);
77
+ const overallScore = total > 0 ? (compliant / total) * 100 : 0;
78
+
79
+ return {
80
+ orgId: this.config.orgId,
81
+ timestamp: new Date().toISOString(),
82
+ states,
83
+ drift: allDrift,
84
+ risks: allRisks,
85
+ graphHash: this.graph.getGraphHash(),
86
+ overallScore,
87
+ };
88
+ }
89
+
90
+ compileNaturalLanguage(framework: FrameworkCode, text: string): string {
91
+ const control = this.compiler.compileNaturalLanguage(framework, text);
92
+ return control.id;
93
+ }
94
+
95
+ async synthesizePlan(
96
+ framework: FrameworkCode,
97
+ currentState: ComplianceState,
98
+ targetScore: number
99
+ ): Promise<CompliancePlan> {
100
+ const nonCompliant = currentState.controlStatuses.filter((s) => s.status !== 'compliant');
101
+ const actions = nonCompliant.map((nc, idx) => ({
102
+ id: `action-${idx}-${Date.now()}`,
103
+ controlId: nc.controlId,
104
+ action: 'remediate' as const,
105
+ resource: nc.controlId,
106
+ evidenceRequired: ['scan', 'config'],
107
+ sla: '4h',
108
+ }));
109
+
110
+ const estimatedCost = actions.length * 500;
111
+
112
+ return {
113
+ id: `plan-${framework}-${Date.now()}`,
114
+ orgId: this.config.orgId,
115
+ framework,
116
+ createdAt: new Date().toISOString(),
117
+ actions,
118
+ estimatedCost,
119
+ estimatedDuration: `${Math.ceil(actions.length / 4)} days`,
120
+ };
121
+ }
122
+
123
+ async executeAudit(
124
+ framework: FrameworkCode,
125
+ reasoningContext: import('./reasoner/NeuroSymbolicReasoner.js').ReasoningContext
126
+ ): Promise<ComplianceAudit> {
127
+ const state = await this.reasoner.reason(reasoningContext);
128
+ const controls = state.controlStatuses.map((cs) => ({
129
+ controlId: cs.controlId,
130
+ status: cs.status === 'compliant' ? 'pass' as const : 'fail' as const,
131
+ evidence: [],
132
+ issues: cs.issues,
133
+ duration: 0,
134
+ }));
135
+
136
+ const passed = controls.filter((c) => c.status === 'pass').length;
137
+ const failed = controls.filter((c) => c.status === 'fail').length;
138
+
139
+ return {
140
+ id: `audit-${framework}-${Date.now()}`,
141
+ orgId: this.config.orgId,
142
+ framework,
143
+ startedAt: new Date().toISOString(),
144
+ completedAt: new Date().toISOString(),
145
+ controls,
146
+ summary: {
147
+ totalControls: controls.length,
148
+ passed,
149
+ failed,
150
+ skipped: 0,
151
+ errors: 0,
152
+ complianceScore: controls.length > 0 ? (passed / controls.length) * 100 : 0,
153
+ criticalFindings: failed,
154
+ highFindings: 0,
155
+ },
156
+ };
157
+ }
158
+
159
+ findCrosswalk(framework: FrameworkCode, controlCode: string): CrosswalkEntry[] {
160
+ return this.compiler.findEquivalent(framework, controlCode);
161
+ }
162
+
163
+ getState(framework: FrameworkCode): ComplianceState | undefined {
164
+ return this.states.get(framework);
165
+ }
166
+
167
+ getGraph(): UnifiedComplianceGraph {
168
+ return this.graph;
169
+ }
170
+
171
+ getCompiler(): RegulationASTCompiler {
172
+ return this.compiler;
173
+ }
174
+
175
+ getReasoner(): NeuroSymbolicReasoner {
176
+ return this.reasoner;
177
+ }
178
+ }
179
+
180
+ type CrosswalkEntry = import('./types.js').CrosswalkEntry;