Semantic Constants vs Hard-Coding: Developer Guide¶
Overview¶
This guide establishes the architectural distinction between legitimate model-specific behavior and forbidden hard-coded logic in ABS Platform service agents. Understanding this distinction is crucial for maintaining clean, data-driven architecture while preserving necessary service semantics.
The Fundamental Problem¶
Service agents must balance two competing concerns: 1. Data-Driven Architecture: All configuration must come from ServicePlanTemplate and setup data 2. Service Model Specificity: Agents must understand service-specific workflows and signal semantics
The challenge: Where is the line between necessary agent specificity and problematic hard-coding?
Core Definitions¶
❌ Hard-Coded Logic (FORBIDDEN)¶
Definition: Configuration data, identifiers, and business parameters that should vary by deployment, location, or business context.
Characteristics:
- Varies by deployment environment
- Business configuration parameters
- Location-specific identifiers
- Operational constraints
Examples:
// ❌ FORBIDDEN: Hard-coded configuration
const HARD_CODED_VALUES = {
station_ids: ['STATION_001', 'STATION_002'],
fleet_id: 'fleet-batteries-nairobi',
max_swaps_per_day: 10,
grace_period_days: 7,
allowed_locations: ['Nairobi', 'Mombasa'],
service_hours: '09:00-18:00',
pricing_tiers: ['standard', 'premium'],
quota_limits: { daily: 5, monthly: 150 }
};
// ❌ FORBIDDEN: Hard-coded business logic
const badValidator = {
check: () => location === 'Nairobi' && swaps_today < 10,
signal: 'SERVICE_AVAILABLE'
};
✅ Model-Specific Behavior (LEGITIMATE)¶
Definition: Protocol-level constants and service semantics that define how a service model interprets signals and implements workflows.
Characteristics: - Part of the service model contract - Consistent across all deployments - Defines inter-process communication semantics - Workflow and signal interpretation rules
Examples:
// ✅ ALLOWED: Semantic constants for signal interpretation
const BSS_SIGNAL_SEMANTICS = {
// IoT signal compression (protocol semantics)
'battery_inserted': 'BATTERY_ISSUED',
'battery_removed': 'BATTERY_RETURNED',
'theft_detected': 'BATTERY_LOST',
'customer_authenticated': 'ACCESS_GRANTED',
// Service milestone definitions
'swap_completed': 'SERVICE_MILESTONE_REACHED',
'maintenance_required': 'ASSET_MAINTENANCE_DUE'
} as const;
// ✅ ALLOWED: FSM compression patterns (model behavior)
const FSM_COMPRESSION_RULES = {
'SERVICE_ACTIVATED': 'service_cycle.DEPOSIT_CONFIRMED',
'ASSET_ALLOCATED': 'service_cycle.BATTERY_ISSUED',
'ASSET_RETURNED': 'service_cycle.BATTERY_RETURNED'
} as const;
// ✅ ALLOWED: Service-specific workflow semantics
const workflowValidator = {
check: () => signal_type === 'battery_inserted' && customer_authenticated,
signal: 'BATTERY_ISSUED'
};
Validation Framework¶
The Semantic Constant Test¶
Ask these questions to determine if a constant is legitimate:
interface SemanticConstantTest {
// ✅ If ALL true → Semantic Constant (ALLOWED)
definesSignalInterpretation: boolean; // Does this define HOW the service interprets signals?
partOfServiceContract: boolean; // Is this part of the service model specification?
consistentAcrossDeployments: boolean; // Would this remain the same across all deployments?
protocolSemantics: boolean; // Does this define inter-process communication?
workflowDefinition: boolean; // Does this define service workflow patterns?
}
The Configuration Data Test¶
Ask these questions to identify hard-coded configuration:
interface ConfigurationDataTest {
// ❌ If ANY true → Configuration Data (FORBIDDEN)
variesByLocation: boolean; // Does this change based on deployment location?
variesByBusinessRules: boolean; // Is this a business parameter that could change?
deploymentSpecific: boolean; // Is this specific to a deployment environment?
operationalParameter: boolean; // Is this an operational constraint/limit?
identifierOrReference: boolean; // Is this an ID, name, or reference?
}
Implementation Patterns¶
Pattern 1: Semantic Constants Module¶
Create a dedicated module for service semantics:
// File: bss-semantic-constants.ts
export const BSS_SEMANTICS = {
// Signal interpretation constants
IOT_SIGNALS: {
'battery_inserted': 'BATTERY_ISSUED',
'battery_removed': 'BATTERY_RETURNED',
'theft_detected': 'BATTERY_LOST',
'damage_reported': 'BATTERY_DAMAGED',
'maintenance_completed': 'ASSET_RESTORED'
},
// MQTT topic semantics
MQTT_PATTERNS: {
'dt/arm/station/*/battery_inserted': 'IOT_BATTERY_INSERTION',
'dt/arm/battery/*/theft_detected': 'IOT_THEFT_ALERT',
'emit/odoo/subscription/*/payment_updated': 'BILLING_UPDATE'
},
// FSM compression mappings
FSM_INPUTS: {
'SERVICE_ACTIVATED': 'service_cycle.DEPOSIT_CONFIRMED',
'ASSET_ALLOCATED': 'service_cycle.BATTERY_ISSUED',
'ASSET_RETURNED': 'service_cycle.BATTERY_RETURNED',
'PAYMENT_CONFIRMED': 'payment_cycle.RENEWAL_PAID'
},
// Service milestone definitions
MILESTONES: {
'swap_initiated': 'SWAP_START',
'swap_completed': 'SWAP_COMPLETE',
'service_terminated': 'SERVICE_END'
}
} as const;
Pattern 2: Configuration Extraction¶
Replace hard-coded values with data-driven extraction:
// ❌ BEFORE: Hard-coded business logic
const oldValidator = {
check: () => station_id === 'STATION_001' &&
location === 'Nairobi' &&
swaps_today < 10,
signal: 'SERVICE_AVAILABLE'
};
// ✅ AFTER: Data-driven configuration
const newValidator = {
check: () => getAllowedStations(template).includes(station_id) &&
getTemplateLocation(template) === location &&
getCurrentSwaps(planState) < getMaxSwaps(template),
signal: 'SERVICE_AVAILABLE'
};
Pattern 3: Validator Arrays with Semantics¶
Combine semantic constants with the Validator Array Pattern:
// ✅ RECOMMENDED: Clean semantic-driven validation
const iotSignalValidators = [
{
check: () => signal_type === BSS_SEMANTICS.IOT_SIGNALS.battery_inserted &&
customer_authenticated,
signal: 'BATTERY_ISSUED'
},
{
check: () => signal_type === BSS_SEMANTICS.IOT_SIGNALS.theft_detected,
signal: 'BATTERY_LOST'
},
{
check: () => signal_type === BSS_SEMANTICS.IOT_SIGNALS.maintenance_completed &&
personnel_verified,
signal: 'ASSET_RESTORED'
}
];
Pattern 4: Configuration Helper Functions¶
Create helper functions to extract data from templates:
// Configuration extraction utilities
export const TemplateUtils = {
getAllowedStations: (template: ServicePlanTemplate): string[] => {
return template.service_bundle.services
.filter(s => s.asset_type === 'FLEET')
.map(s => extractStationIds(s.asset_reference))
.flat();
},
getMaxSwapsPerDay: (template: ServicePlanTemplate): number => {
const quotaConfig = template.agent_config.quota_settings;
return quotaConfig?.daily_limit ?? Infinity;
},
getGracePeriodDays: (template: ServicePlanTemplate): number => {
return template.contract_terms.grace_period_days;
},
getAllowedBatteryTypes: (template: ServicePlanTemplate): string[] => {
return template.service_bundle.services
.filter(s => s.asset_type === 'FLEET')
.map(s => s.access_control.battery_types)
.flat();
}
};
Architecture Benefits¶
1. Clear Separation of Concerns¶
- Semantic constants: Define service model behavior (agent responsibility)
- Configuration data: Varies by deployment (template responsibility)
- Clean boundaries: Prevents mixing protocol semantics with business config
2. Maintainability¶
- Protocol changes: Update semantic constants (rare, controlled changes)
- Business changes: Update setup data (frequent, data-driven changes)
- Version management: Semantic constants versioned with agent, config with templates
3. Testability¶
- Unit tests: Validate signal compression logic using semantic constants
- Integration tests: Test various configurations using different templates
- Consistent behavior: Same semantic constants across test environments
4. Scalability¶
- New deployments: Only require new template configurations
- Service expansion: Semantic constants define consistent service behavior
- Multi-region: Same agent logic, different configuration data
Common Mistakes and Solutions¶
Mistake 1: Location-Specific Logic¶
// ❌ WRONG: Hard-coded location logic
if (location === 'Nairobi') {
return { maxSwaps: 10, currency: 'KES' };
}
// ✅ CORRECT: Template-driven location logic
return {
maxSwaps: getMaxSwaps(template),
currency: getTemplateCurrency(template)
};
Mistake 2: Business Rule Constants¶
// ❌ WRONG: Hard-coded business rules
const GRACE_PERIOD_DAYS = 7;
const MAX_OUTSTANDING_AMOUNT = 5000;
// ✅ CORRECT: Configuration-driven business rules
const gracePeriod = template.contract_terms.grace_period_days;
const maxOutstanding = template.contract_terms.credit_limit;
Mistake 3: Service Identifier Dependencies¶
// ❌ WRONG: Hard-coded service dependencies
if (serviceId === 'svc-battery-fleet-kenya-premium') {
return { tier: 'premium', batteryTypes: ['TYPE_A', 'TYPE_B'] };
}
// ✅ CORRECT: Service metadata extraction
const service = getServiceById(template, serviceId);
return {
tier: service.access_control.quality_tier,
batteryTypes: service.access_control.battery_types
};
Testing Guidelines¶
Test Semantic Constants¶
describe('BSS Signal Semantics', () => {
it('should compress IoT signals correctly', () => {
const signal = BSS_SEMANTICS.IOT_SIGNALS.battery_inserted;
expect(signal).toBe('BATTERY_ISSUED');
});
it('should map FSM inputs consistently', () => {
const fsmInput = BSS_SEMANTICS.FSM_INPUTS.ASSET_ALLOCATED;
expect(fsmInput).toBe('service_cycle.BATTERY_ISSUED');
});
});
Test Configuration Extraction¶
describe('Template Configuration', () => {
it('should extract station IDs from template', () => {
const stations = TemplateUtils.getAllowedStations(testTemplate);
expect(stations).toEqual(['STA-001', 'STA-002']);
});
it('should handle different business rules per template', () => {
const maxSwaps = TemplateUtils.getMaxSwapsPerDay(premiumTemplate);
expect(maxSwaps).toBe(20); // Premium template allows more swaps
});
});
Migration Strategy¶
Step 1: Identify Hard-Coded Values¶
# Search for potential hard-coded values
grep -r "STATION_" src/
grep -r "fleet-" src/
grep -r "max.*:" src/
Step 2: Extract Semantic Constants¶
// Create semantic constants module
export const SERVICE_SEMANTICS = {
// Move signal interpretation logic here
};
Step 3: Create Configuration Helpers¶
// Create template utility functions
export const ConfigUtils = {
// Move configuration extraction logic here
};
Step 4: Update Validators¶
// Replace hard-coded values in validator arrays
const updatedValidators = validators.map(v => ({
check: () => replaceHardCodedLogic(v.check),
signal: v.signal
}));
Conclusion¶
The distinction between semantic constants and hard-coded logic is fundamental to maintaining clean, scalable service architecture:
- Semantic Constants: Enable service model specificity while maintaining consistency
- Configuration Data: Ensures flexibility and data-driven behavior
- Clear Boundaries: Prevent mixing protocol semantics with business configuration
Following these patterns ensures that ABS Platform service agents remain both service-specific and deployment-agnostic, supporting the core architectural principles of the DIRAC Framework.