@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
190 lines • 8.38 kB
JavaScript
/**
* Business Logic Preserver
* @description Identifies and preserves user-specified business logic during auto-correction
*
* Purpose: Prevent auto-correction from changing business-critical values like
* traffic allocation percentages, weights, and other values that affect experiment behavior.
*/
import { getLogger } from '../logging/Logger.js';
export class BusinessLogicPreserver {
logger = getLogger();
// Fields that represent business logic, not just technical formatting
BUSINESS_CRITICAL_FIELDS = new Set([
'traffic_allocation',
'holdback',
'percentage_included',
'weight',
'allocation_policy',
'enabled',
'status',
'archived',
'variations', // Array of variations affects experiment behavior
'default_variation_key',
'whitelist' // User whitelist is business logic
]);
// Fields where any non-zero value was likely intentional
PERCENTAGE_FIELDS = new Set([
'traffic_allocation',
'holdback',
'percentage_included',
'weight'
]);
/**
* Check if a field change affects business logic
*/
isBusinessCriticalChange(field, originalValue, newValue) {
// Always critical if in the critical fields list
if (this.BUSINESS_CRITICAL_FIELDS.has(field)) {
// For percentage fields, changing from a high value to a low value is critical
if (this.PERCENTAGE_FIELDS.has(field)) {
const original = Number(originalValue);
const suggested = Number(newValue);
// If user specified 100% (10000) and we're suggesting 1% (100), that's critical
if (original > 1000 && suggested < 200) {
return true;
}
// Any change > 10% is business critical
if (Math.abs(original - suggested) > 1000) {
return true;
}
}
// For boolean fields, any change is critical
if (typeof originalValue === 'boolean' && originalValue !== newValue) {
return true;
}
// For status fields, certain changes are critical
if (field === 'status' || field === 'archived' || field === 'enabled') {
return originalValue !== newValue;
}
}
return false;
}
/**
* Analyze proposed changes and identify business logic modifications
*/
analyzeChanges(original, suggested, context) {
const changes = [];
const warnings = [];
let hasBusinessCriticalChanges = false;
// Check each field in the suggested payload
for (const [field, suggestedValue] of Object.entries(suggested)) {
const originalValue = original[field];
// Skip if values are the same
if (originalValue === suggestedValue) {
continue;
}
// Skip if original had no value (not user-specified)
if (originalValue === undefined || originalValue === null) {
continue;
}
const isCritical = this.isBusinessCriticalChange(field, originalValue, suggestedValue);
if (isCritical) {
hasBusinessCriticalChanges = true;
const change = {
field,
originalValue,
suggestedValue,
reason: this.getChangeReason(field, originalValue, suggestedValue),
isBusinessCritical: true,
requiresConfirmation: true
};
changes.push(change);
// Add warning for percentage changes
if (this.PERCENTAGE_FIELDS.has(field)) {
const originalPercent = (Number(originalValue) / 100).toFixed(1);
const suggestedPercent = (Number(suggestedValue) / 100).toFixed(1);
warnings.push(`⚠️ ${field} would change from ${originalPercent}% to ${suggestedPercent}%. ` +
`This significantly affects experiment behavior. Add "_preserve_business_values": true to keep original value.`);
}
}
}
this.logger.info({
hasBusinessCriticalChanges,
changeCount: changes.length,
fields: changes.map(c => c.field)
}, 'Business logic preservation analysis complete');
return {
preserved: !hasBusinessCriticalChanges,
changes,
warnings
};
}
/**
* Get human-readable reason for the change
*/
getChangeReason(field, originalValue, suggestedValue) {
if (field === 'traffic_allocation') {
if (Number(originalValue) === 10000 && Number(suggestedValue) === 100) {
return 'Reducing from 100% to 1% (safe default for new experiments)';
}
return `Changing traffic allocation from ${(Number(originalValue) / 100).toFixed(1)}% to ${(Number(suggestedValue) / 100).toFixed(1)}%`;
}
if (field === 'holdback') {
return `Changing holdback from ${(Number(originalValue) / 100).toFixed(1)}% to ${(Number(suggestedValue) / 100).toFixed(1)}%`;
}
if (field === 'weight') {
return `Changing variation weight from ${(Number(originalValue) / 100).toFixed(1)}% to ${(Number(suggestedValue) / 100).toFixed(1)}%`;
}
if (field === 'enabled' || field === 'archived' || field === 'status') {
return `Changing ${field} from ${originalValue} to ${suggestedValue}`;
}
return `Changing ${field} value`;
}
/**
* Apply preservation logic to maintain user-specified values
*/
preserveBusinessLogic(original, suggested, options) {
const preserved = { ...suggested };
const analysis = this.analyzeChanges(original, suggested);
if (options?.preserveAll || original._preserve_business_values) {
// Preserve all business-critical values
for (const change of analysis.changes) {
if (change.isBusinessCritical) {
preserved[change.field] = change.originalValue;
this.logger.info({
field: change.field,
preservedValue: change.originalValue,
rejectedValue: change.suggestedValue
}, 'Preserved user-specified business value');
}
}
}
else if (options?.preserveFields) {
// Preserve specific fields
for (const field of options.preserveFields) {
if (original[field] !== undefined) {
preserved[field] = original[field];
}
}
}
else {
// Default: Preserve percentage fields that would change significantly
for (const change of analysis.changes) {
if (this.PERCENTAGE_FIELDS.has(change.field)) {
const originalNum = Number(change.originalValue);
const suggestedNum = Number(change.suggestedValue);
// Preserve if changing by more than 90% (e.g., 10000 to 100)
if (originalNum > 0 && (suggestedNum / originalNum) < 0.1) {
preserved[change.field] = change.originalValue;
preserved._business_logic_preserved = true;
preserved._preserved_fields = preserved._preserved_fields || [];
preserved._preserved_fields.push({
field: change.field,
reason: change.reason,
original: change.originalValue,
suggested: change.suggestedValue
});
}
}
}
}
// Add warnings if we preserved values
if (preserved._business_logic_preserved) {
preserved._preservation_warnings = analysis.warnings;
}
return preserved;
}
}
// Export singleton instance
export const businessLogicPreserver = new BusinessLogicPreserver();
//# sourceMappingURL=BusinessLogicPreserver.js.map