/** * Ensemble Element - Orchestrates multiple elements working together * * Ensembles allow combining multiple elements (personas, skills, templates, agents, memories) * into cohesive units with controlled activation, conflict resolution, and shared context. * * ARCHITECTURE: * - Extends BaseElement for standard element behavior * - Pure business logic (no file operations) * - Portfolio-agnostic (PortfolioManager passed to activate()) * * SECURITY MEASURES: * 1. Circular dependency detection with DFS algorithm * 2. Resource limits (max elements, nesting depth, activation time) * 3. Input sanitization for all user-provided data * 4. Activation timeout protection * 5. Context size limits to prevent memory exhaustion * 6. Audit logging for security events * 7. Condition validation to prevent code injection */ import { BaseElement } from '../BaseElement.js'; import { IElement, ElementValidationResult } from '../../types/elements/index.js'; import { PortfolioManager } from '../../portfolio/PortfolioManager.js'; import { MetadataService } from '../../services/MetadataService.js'; import { EnsembleMetadata, EnsembleElement, EnsembleActivationResult, EnsembleActivationMetrics } from './types.js'; import { ResolvedEnsembleLimits } from './constants.js'; /** * Ensemble class - Orchestrates multiple elements as a cohesive unit * * Extends BaseElement to inherit: * - Standard validation * - Serialization * - Metadata management * - ID generation * - Status tracking */ export declare class Ensemble extends BaseElement implements IElement { metadata: EnsembleMetadata; private elements; private elementInstances; private readonly MAX_INSTANCE_CACHE_SIZE; private instanceAccessTimes; private sharedContext; private activationInProgress; private lastActivationResult?; private activationMetrics; private _effectiveLimits; /** * Get effective limits for this ensemble * * Resolves limits in priority order: * 1. Per-ensemble overrides (from metadata.resourceLimits) * 2. Global configuration (setGlobalEnsembleLimits()) * 3. Environment variables (ENSEMBLE_MAX_*) * 4. Default values * * Results are cached for performance. Call invalidateLimitsCache() to refresh. * * @returns Resolved limits object */ getEffectiveLimits(): ResolvedEnsembleLimits; /** * Invalidate cached limits (call when resourceLimits changes) */ invalidateLimitsCache(): void; constructor(metadata: Partial, elements: EnsembleElement[] | undefined, metadataService: MetadataService); /** * Find an element by name in the elements array * @param name - Element name to search for * @returns Element if found, undefined otherwise */ private findElementByName; /** * Check if an element exists in the array * @param name - Element name to check * @returns true if element exists, false otherwise */ private hasElement; /** * Remove an element from the array by name * @param name - Element name to remove * @returns true if element was removed, false if not found */ private removeElementByName; /** * Add or update an element in the array * @param element - Element to add or update * @returns true if element was added, false if updated */ private setElement; /** * Sync elements array to metadata (single source of truth) */ private syncElementsToMetadata; /** * Sync the internal elements array from metadata.elements * * Use this after metadata.elements has been modified externally * (e.g., via editElement) to ensure internal state is consistent. */ syncElementsFromMetadata(): void; /** * Load element references from metadata * Called during construction to populate elements array */ private loadElementsFromMetadata; /** * Add an element to the ensemble * * @param element - Element configuration to add * @throws Error if element would create circular dependency or exceed limits */ addElement(element: EnsembleElement): void; /** * Update an element's configuration in the ensemble * * @param elementName - Name of element to update * @param updates - Partial element configuration to merge with existing * @throws Error if element not found or activation in progress */ updateElement(elementName: string, updates: Partial): void; /** * Remove an element from the ensemble * * @param elementName - Name of element to remove * @throws Error if element not found or activation in progress */ removeElement(elementName: string): void; /** * Activate the element (BaseElement interface implementation) * For ensembles, this is a no-op. Use activateEnsemble() for full activation. */ activate(): Promise; /** * Activate the ensemble and all its elements based on strategy * * @param portfolioManager - Portfolio manager for loading element instances * @param managers - Element managers for loading different element types * @param nestingDepth - Current nesting depth (0 for top-level, increments for nested) * @returns Activation result with success status and element results */ activateEnsemble(portfolioManager: PortfolioManager, managers: import('./types.js').ElementManagers, nestingDepth?: number): Promise; /** * Helper to create structured log context for ensemble operations * @private */ private getLogContext; /** * Perform the actual activation logic * Extracted to separate method for timeout handling */ private performActivation; /** * Deactivate the ensemble and all its elements */ deactivate(): Promise; /** * Validate the ensemble configuration * Overrides BaseElement.validate() to add ensemble-specific checks */ validate(): ElementValidationResult; /** * Determine activation order based on strategy */ private getActivationOrder; /** * Topological sort for dependency-based activation order */ private topologicalSort; /** * Activate elements sequentially in dependency order */ private activateSequential; /** * Activate all elements simultaneously */ private activateAll; /** * Activate elements by priority (highest first) */ private activatePriority; /** * Activate elements based on conditions */ private activateConditional; /** * Activate a single element and track results */ private activateSingleElement; /** * Activate an element via its type manager's activation method. * This registers the element in the manager's active set so that * get_active_elements correctly reports it. * * @returns true if activation went through a manager, false if no manager available * @see Issue #1769 - Ensemble activation not registering with type managers */ private activateViaManager; /** * Load an element instance from portfolio */ private loadElementInstance; /** * Normalize element type to singular form */ private normalizeElementType; /** * Get the appropriate manager for an element type */ private getManagerForType; /** * Get list of available manager types */ private getAvailableManagerTypes; /** * Find an element in the manager using fuzzy name matching */ private findElementInManager; /** * Check if element name matches target using fuzzy matching * Handles exact match, slugified match, and bidirectional slug matching * * SECURITY: Normalizes Unicode to prevent composed vs decomposed character mismatches. * This is defense-in-depth; element names are already validated by ELEMENT_NAME_PATTERN * which blocks non-ASCII characters. * * @param actualName - Name from element manager (filesystem) * @param targetName - Name from ensemble configuration (user input) * @returns true if names match exactly or after slugification */ private matchesElementName; /** * Capitalize first letter of a string */ private capitalizeFirst; /** * Evict the oldest accessed instance from the cache * Implements LRU (Least Recently Used) eviction policy */ private evictOldestInstance; /** * Clear the element instance cache * Useful for freeing memory or forcing reload of elements */ clearInstanceCache(): void; /** * Build a type-safe context for condition evaluation * * Constructs a ConditionContext object with all values needed to evaluate * an element's activation condition. The context is immutable and only * exposes safe, read-only data. * * SECURITY GUARANTEES: * - Only exposes documented, safe properties * - No access to element instances or methods * - All nested objects are readonly (shallow immutability) * - Context values remain type-checked as unknown * - No exposure of internal ensemble state * * PERFORMANCE: * - Minimal object allocation (reuses maps where possible) * - O(1) lookups for element and context data * - Lazy computation of resource metrics * - No deep cloning (uses readonly type guards) * * @param element - The element whose condition is being evaluated * @param activationStartTime - Timestamp when ensemble activation began * @param result - Current activation result (for tracking progress) * @returns Context builder result with context object and metadata * * @example * ```typescript * const contextResult = this.buildConditionContext( * elementConfig, * startTime, * activationResult * ); * * // Use context in evaluation * const shouldActivate = evaluateExpression( * element.condition, * contextResult.context * ); * ``` */ private buildConditionContext; /** * Evaluate an activation condition in a secure VM sandbox * * SECURITY IMPLEMENTATION: * This method evaluates user-provided conditions using Node.js VM module with multiple * layers of defense: * * 1. **Sandboxed Execution**: Uses vm.createContext() with frozen objects * - No access to require, process, global, or other Node.js APIs * - All context objects are deeply frozen (immutable) * - No prototype chain access to prevent pollution * * 2. **Timeout Protection**: 100ms maximum evaluation time * - Prevents infinite loops and ReDoS attacks * - Terminates long-running expressions * * 3. **Input Validation**: Pre-validated by isValidCondition() * - Dangerous patterns blocked (eval, Function, require, etc.) * - Only safe operators allowed * - Maximum length enforced * * 4. **Type Safety**: Enforces boolean return values * - Non-boolean results treated as false * - Prevents side effects from non-expression statements * * 5. **Error Handling**: Fail-secure principle * - Evaluation errors result in non-activation (return false) * - All failures logged to SecurityMonitor * - No error details leaked to user * * SUPPORTED FEATURES: * - Context variable access: context.*, element.*, state.*, resources.*, environment.* * - Comparison operators: ==, !=, ===, !==, >, <, >=, <= * - Logical operators: &&, ||, ! * - Parentheses for grouping * - Array/object property access * * Example valid conditions: * - "element.priority >= 80" * - "context.security_review === true" * - "state.activatedCount > 3 && element.role === 'primary'" * - "context.environment === 'production' || context.override" * * @param condition - The condition string to evaluate (pre-validated) * @param element - The element whose condition is being evaluated * @param activationStartTime - When activation began (for resource tracking) * @param result - Current activation result (for state tracking) * @returns true if condition evaluates to true, false otherwise * @private * @security Multiple defense layers: sandbox, timeout, validation, immutability */ private evaluateCondition; /** * Check if a condition syntax is valid * * Validates that a condition: * 1. Matches the safe character pattern (alphanumeric, operators, etc.) * 2. Does not contain dangerous keywords or operators * 3. Is not empty or whitespace-only * * SECURITY: Multi-layer validation approach * - Layer 1: Positive pattern match (CONDITION_PATTERN) allows only safe characters * - Layer 2: Negative pattern match (DANGEROUS_CONDITION_PATTERNS) blocks code injection * - Layer 3: VM sandbox with timeout during evaluation (see evaluateCondition) * This defense-in-depth strategy ensures conditions cannot execute arbitrary code * * @param condition - The condition string to validate * @returns true if condition is safe, false otherwise */ private isValidCondition; /** * Check if adding an element would create a circular dependency */ private wouldCreateCircularDependency; /** * Find circular dependency path for error reporting */ private findCircularDependency; /** * Detect all circular dependencies in the ensemble */ private detectAllCircularDependencies; /** * Set a value in shared context * * SECURITY: Size limits prevent memory exhaustion attacks * - Individual value size capped at MAX_CONTEXT_VALUE_SIZE (10KB) * - Total context entries capped at MAX_CONTEXT_SIZE (1000 entries) * - Prevents DoS via unbounded context growth * These limits allow legitimate use while blocking malicious payloads * * @param key - Context key (sanitized) * @param value - Value to store (size-limited) * @param ownerElementName - Element setting this value (validated) */ setContextValue(key: string, value: unknown, ownerElementName: string): void; /** * Get a value from shared context */ getContextValue(key: string): unknown; /** * Clear all or specific context values * * @param ownerElementName - Optional: only clear values owned by this element * @throws Error if trying to clear during activation */ clearContext(ownerElementName?: string): void; /** * Type guard to check if a value is a plain object (not null, not array) */ private isPlainObject; /** * Resolve a context conflict based on configured strategy */ private resolveConflict; /** * Update activation metrics after an activation attempt * @private */ private updateActivationMetrics; /** * Get activation metrics for performance monitoring * Returns a snapshot of current metrics */ getActivationMetrics(): Readonly; /** * Get the last activation result */ getLastActivationResult(): EnsembleActivationResult | undefined; /** * Get all elements in the ensemble */ getElements(): EnsembleElement[]; /** * Get a specific element by name */ getElement(name: string): EnsembleElement | undefined; } export type { EnsembleMetadata, EnsembleElement } from './types.js'; //# sourceMappingURL=Ensemble.d.ts.map