claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
249 lines (225 loc) • 8.11 kB
text/typescript
import { TrustLevel, isOperationAllowed } from '../domain/entities/trust-level.js';
import { type PIIAction, type PIIType, type PIIDetection, type PIIPolicyConfig } from '../domain/services/pii-pipeline-service.js';
import { type FederationMessageType } from '../domain/entities/federation-envelope.js';
export type FederationClaimType =
| 'federation:discover'
| 'federation:connect'
| 'federation:read'
| 'federation:write'
| 'federation:admin'
| 'federation:memory'
| 'federation:spawn';
export interface SecurityPolicy {
readonly maxMessageSizeBytes: number;
readonly maxMessagesPerMinute: number;
readonly allowedMessageTypes: readonly FederationMessageType[];
readonly requirePiiScan: boolean;
readonly requireAiDefenceScan: boolean;
readonly minTrustLevelForWrite: TrustLevel;
readonly minTrustLevelForMemory: TrustLevel;
readonly minTrustLevelForSpawn: TrustLevel;
}
export interface PolicyEvaluationResult {
readonly allowed: boolean;
readonly reason: string;
readonly requiredClaims: readonly FederationClaimType[];
readonly trustLevelSufficient: boolean;
readonly piiAction?: PIIAction;
}
const DEFAULT_SECURITY_POLICY: SecurityPolicy = {
maxMessageSizeBytes: 1_048_576,
maxMessagesPerMinute: 120,
allowedMessageTypes: [
'task-assignment', 'memory-query', 'memory-response',
'context-share', 'status-broadcast', 'heartbeat',
'trust-change', 'topology-change', 'agent-spawn',
],
requirePiiScan: true,
requireAiDefenceScan: true,
minTrustLevelForWrite: TrustLevel.ATTESTED,
minTrustLevelForMemory: TrustLevel.TRUSTED,
minTrustLevelForSpawn: TrustLevel.PRIVILEGED,
};
const CLAIMS_FOR_MESSAGE_TYPE: Record<FederationMessageType, FederationClaimType[]> = {
'task-assignment': ['federation:write'],
'memory-query': ['federation:read', 'federation:memory'],
'memory-response': ['federation:read', 'federation:memory'],
'context-share': ['federation:write'],
'status-broadcast': ['federation:read'],
'trust-change': ['federation:admin'],
'topology-change': ['federation:admin'],
'agent-spawn': ['federation:spawn'],
'heartbeat': ['federation:connect'],
'challenge': ['federation:connect'],
'challenge-response': ['federation:connect'],
'handshake-init': ['federation:connect'],
'handshake-accept': ['federation:connect'],
'handshake-reject': ['federation:connect'],
'session-terminate': ['federation:connect'],
// ADR-101 Component C — federated claims operations.
// claim-event: broadcasts a ClaimDomainEvent (state mutation across peers),
// same authorization shape as task-assignment.
'claim-event': ['federation:write'],
// agent-handoff: transfers claim ownership to a remote agent. Requires
// both the write capability (state mutation) AND spawn-lifecycle authority
// (handoff is a sibling of agent-spawn — both reshape who owns work).
// Matches the consensus-gated security posture in ADR-101.
'agent-handoff': ['federation:write', 'federation:spawn'],
};
export interface PolicyEngineDeps {
checkClaim: (claim: FederationClaimType) => boolean;
}
export class PolicyEngine {
private readonly deps: PolicyEngineDeps;
private readonly securityPolicy: SecurityPolicy;
private readonly piiPolicy: PIIPolicyConfig;
private readonly rateLimitCounters: Map<string, { count: number; windowStart: number }>;
constructor(
deps: PolicyEngineDeps,
securityPolicy?: Partial<SecurityPolicy>,
piiPolicy?: PIIPolicyConfig,
) {
this.deps = deps;
this.securityPolicy = { ...DEFAULT_SECURITY_POLICY, ...securityPolicy };
this.piiPolicy = piiPolicy ?? {
defaultAction: 'redact',
overrides: {},
hashAlgorithm: 'sha256',
hashSalt: '',
redactionPlaceholder: '[REDACTED:{type}]',
};
this.rateLimitCounters = new Map();
}
evaluateMessage(
messageType: FederationMessageType,
trustLevel: TrustLevel,
messageSizeBytes: number,
sourceNodeId: string,
): PolicyEvaluationResult {
if (!this.securityPolicy.allowedMessageTypes.includes(messageType)) {
return {
allowed: false,
reason: `Message type '${messageType}' is not allowed by security policy`,
requiredClaims: [],
trustLevelSufficient: false,
};
}
if (messageSizeBytes > this.securityPolicy.maxMessageSizeBytes) {
return {
allowed: false,
reason: `Message size ${messageSizeBytes} exceeds maximum ${this.securityPolicy.maxMessageSizeBytes}`,
requiredClaims: [],
trustLevelSufficient: true,
};
}
if (!this.checkRateLimit(sourceNodeId)) {
return {
allowed: false,
reason: `Rate limit exceeded for node ${sourceNodeId}`,
requiredClaims: [],
trustLevelSufficient: true,
};
}
const requiredClaims = CLAIMS_FOR_MESSAGE_TYPE[messageType] ?? [];
const missingClaims = requiredClaims.filter(c => !this.deps.checkClaim(c));
if (missingClaims.length > 0) {
return {
allowed: false,
reason: `Missing required claims: ${missingClaims.join(', ')}`,
requiredClaims,
trustLevelSufficient: true,
};
}
const operationForType = this.getOperationForMessageType(messageType);
if (!isOperationAllowed(trustLevel, operationForType)) {
return {
allowed: false,
reason: `Trust level ${trustLevel} insufficient for operation '${operationForType}'`,
requiredClaims,
trustLevelSufficient: false,
};
}
return {
allowed: true,
reason: 'Policy check passed',
requiredClaims,
trustLevelSufficient: true,
};
}
evaluatePiiDetection(detection: PIIDetection, trustLevel: TrustLevel): PIIAction {
const override = this.piiPolicy.overrides[detection.type];
if (override?.trustLevelOverride?.[trustLevel]) {
return override.trustLevelOverride[trustLevel]!;
}
if (override?.action) {
return override.action;
}
return this.piiPolicy.defaultAction;
}
evaluateTrustChange(
currentLevel: TrustLevel,
proposedLevel: TrustLevel,
): { allowed: boolean; requiresHumanApproval: boolean; reason: string } {
if (proposedLevel > currentLevel && proposedLevel === TrustLevel.PRIVILEGED) {
return {
allowed: true,
requiresHumanApproval: true,
reason: 'Elevation to PRIVILEGED requires institutional attestation and human approval',
};
}
if (proposedLevel > currentLevel) {
return {
allowed: true,
requiresHumanApproval: false,
reason: `Trust upgrade from ${currentLevel} to ${proposedLevel} allowed`,
};
}
if (proposedLevel < currentLevel) {
return {
allowed: true,
requiresHumanApproval: false,
reason: `Trust downgrade from ${currentLevel} to ${proposedLevel} allowed`,
};
}
return {
allowed: false,
requiresHumanApproval: false,
reason: 'No trust level change',
};
}
private checkRateLimit(nodeId: string): boolean {
const now = Date.now();
const counter = this.rateLimitCounters.get(nodeId);
if (!counter || now - counter.windowStart > 60_000) {
this.rateLimitCounters.set(nodeId, { count: 1, windowStart: now });
return true;
}
counter.count++;
return counter.count <= this.securityPolicy.maxMessagesPerMinute;
}
private getOperationForMessageType(messageType: FederationMessageType): string {
switch (messageType) {
case 'memory-query':
case 'memory-response':
return 'query-redacted';
case 'context-share':
return 'share-context';
case 'task-assignment':
return 'send';
case 'agent-spawn':
return 'remote-spawn';
case 'status-broadcast':
return 'status';
case 'heartbeat':
case 'challenge':
case 'challenge-response':
case 'handshake-init':
case 'handshake-accept':
case 'handshake-reject':
case 'session-terminate':
return 'ping';
default:
return 'send';
}
}
}