Contract System Issues
Contract System Issues
Section titled “Contract System Issues”The contract system provides type-safe service contracts with compile-time validation. This guide helps diagnose and resolve contract validation, broadcasting, and schema generation issues.
Quick Fix
Section titled “Quick Fix”# Check contract validation errorsdocker logs my-service 2>&1 | grep -E "contract|validation|schema"
# Test contract validationcurl -X POST http://localhost:3000/api/command \ -H "Content-Type: application/json" \ -d '{"command":"CreateUser","data":{"email":"test@example.com"}}'
# Check contract broadcastingdocker logs service-discovery 2>&1 | grep "Contract received"
# Verify service contracts registeredcurl http://localhost:3001/api/services/my-service/contracts | jqCommon Problems
Section titled “Common Problems”1. Contract Validation Failed
Section titled “1. Contract Validation Failed”Symptoms:
- Error: “CONTRACT_VALIDATION_ERROR”
- Input/output validation failures
- Missing required fields errors
- Type mismatch errors
Diagnostic Steps:
# Check validation errors in logsdocker logs my-service 2>&1 | grep "Validation failed"
# Test contract schemacurl http://localhost:3001/api/services/my-service/contracts | jq '.commands[]'
# Verify handler input/output typesgrep -A10 "@CommandHandler" src/commands/CreateUserHandler.tsCommon Errors:
// From contract-validator.tsthrow new Error(`Command "${command}" not found in contract`); // Line 46throw new Error(`Query "${query}" not found in contract`); // Line 67throw new Error(`Event "${event}" not found in contract`); // Line 88throw new Error(`${operationType} "${operationName}" not found in contract`); // Line 122throw new Error(`No schema found for ${context}`); // Line 142throw new Error(`Validation failed for ${context}:\n${errorMessages.join('\n')}`); // Line 155Solution:
A. Define Complete Contract
import { Contract, Field } from '@banyanai/platform-contract-system';import { IsString, IsEmail, MinLength } from 'class-validator';
// ✓ CORRECT: Complete contract with all required decorators@Contract()export class CreateUserCommand { @Field() @IsEmail() email!: string;
@Field() @IsString() @MinLength(8) password!: string;
@Field() @IsString() name!: string;}
// ❌ WRONG: Missing decoratorsexport class CreateUserCommand { email!: string; // Missing @Field() and validators password!: string; // Missing @Field() and validators name!: string; // Missing @Field() and validators}B. Ensure All Fields Have Decorators
// Every field needs @Field() decorator@Contract()export class UpdateUserCommand { @Field() @IsString() userId!: string;
@Field() // Required even for optional fields @IsString() @IsOptional() name?: string;
@Field() @IsString() @IsOptional() bio?: string;}C. Match Handler Return Type to Contract
// Contract output schema@Contract()export class CreateUserResult { @Field() @IsString() userId!: string;
@Field() @IsEmail() email!: string;}
// Handler MUST return matching type@CommandHandler(CreateUserCommand)export class CreateUserHandler { async handle(command: CreateUserCommand): Promise<CreateUserResult> { const user = await this.createUser(command);
// ✓ CORRECT: Return matches contract return { userId: user.userId, email: user.email };
// ❌ WRONG: Missing required field // return { // userId: user.userId // // email missing! // }; }}2. Contract Broadcasting Failed
Section titled “2. Contract Broadcasting Failed”Symptoms:
- Service contracts not appearing in service discovery
- Other services cannot find contracts
- Contract updates not propagated
- “Contract not found” errors
Diagnostic Steps:
# Check contract broadcasting logsdocker logs my-service 2>&1 | grep "Broadcasting contract"
# Verify contracts in service discoverycurl http://localhost:3001/api/services | jq '.services[] | select(.name=="my-service") | .contracts'
# Check message bus for contract eventsdocker logs rabbitmq 2>&1 | grep "contracts"
# Test message bus connectivitydocker exec my-service nc -zv rabbitmq 5672Common Causes:
A. Message Bus Not Connected
# Service must connect to RabbitMQ before broadcastingdocker logs my-service 2>&1 | grep -E "RabbitMQ|MessageBus"
# Should see:# [MessageBusClient] Connected to RabbitMQ# [ContractBroadcaster] Broadcasting contracts for my-serviceSolution:
Ensure service starts with message bus configuration:
services: my-service: environment: - RABBITMQ_URL=amqp://admin:admin123@rabbitmq:5672 depends_on: rabbitmq: condition: service_healthyB. Contract Export Issues
// ❌ WRONG: Contract not exported@Contract()class CreateUserCommand { // Won't be discovered!}
// ✓ CORRECT: Exported contract@Contract()export class CreateUserCommand { // Will be discovered and broadcast}C. Service Discovery Not Running
# Check service discovery statusdocker ps | grep service-discovery
# If not running, start itdocker compose up -d service-discovery
# Wait for readydocker logs service-discovery 2>&1 | grep "started successfully"3. Permission Validation Errors
Section titled “3. Permission Validation Errors”Symptoms:
- Error: “PERMISSION_VALIDATION_ERROR”
- Invalid permission format errors
- Permission decorator rejected
- “must use ‘resource:action’ format” errors
Diagnostic Steps:
# Check permission format errorsdocker logs my-service 2>&1 | grep "permission"
# View contract permissionscurl http://localhost:3001/api/services/my-service/contracts | jq '.commands[].requiredPermissions'Common Errors:
// From contract-scanner.tsthrow new Error('At least one permission must be specified'); // Line 164
// Permission format validation (from Permission.ts domain model)throw new Error('Permission must be a non-empty string');throw new Error(`Permission must follow 'service:action' format, got: ${permission}`);throw new Error(`Permission service and action cannot be empty, got: ${permission}`);throw new Error( `Permission service contains invalid characters (only lowercase letters, numbers, hyphens allowed), got: ${service}`);throw new Error( `Permission action contains invalid characters (only lowercase letters, numbers, hyphens allowed), got: ${action}`);Solution:
A. Use Correct Permission Format
import { RequiresPermission } from '@banyanai/platform-base-service';
// ✓ CORRECT: Proper permission format@CommandHandler(CreateUserCommand)@RequiresPermission('users:create')export class CreateUserHandler { }
@CommandHandler(UpdateUserCommand)@RequiresPermission('users:update')export class UpdateUserHandler { }
@CommandHandler(DeleteUserCommand)@RequiresPermission('users:delete')export class DeleteUserHandler { }
// ❌ WRONG: Various invalid formats@RequiresPermission('users-create') // No colon@RequiresPermission('Users:Create') // Uppercase@RequiresPermission('users:') // Empty action@RequiresPermission(':create') // Empty resource@RequiresPermission('user service:create') // Space@RequiresPermission('users:create!') // Invalid characterB. Multiple Permissions
// Handler requires multiple permissions@CommandHandler(TransferFundsCommand)@RequiresPermission(['accounts:read', 'accounts:transfer'])export class TransferFundsHandler { }C. Validate Permission Strings
function validatePermission(permission: string): void { if (!permission || typeof permission !== 'string') { throw new Error('Permission must be a non-empty string'); }
if (!permission.includes(':')) { throw new Error(`Permission must follow 'service:action' format, got: ${permission}`); }
const [resource, action] = permission.split(':');
if (!resource || !action) { throw new Error(`Permission service and action cannot be empty, got: ${permission}`); }
const validPattern = /^[a-z0-9-]+$/; if (!validPattern.test(resource)) { throw new Error(`Invalid permission resource: ${resource}`); }
if (!validPattern.test(action)) { throw new Error(`Invalid permission action: ${action}`); }}4. Schema Generation Errors
Section titled “4. Schema Generation Errors”Symptoms:
- Error: “SCHEMA_GENERATION_ERROR”
- GraphQL schema generation failures
- Duplicate type names
- Invalid type definitions
Diagnostic Steps:
# Check schema generation logsdocker logs api-gateway 2>&1 | grep -E "schema|GraphQL"
# View generated schemacurl http://localhost:3000/api/graphql | jq
# Check for type conflictsgrep -r "@Contract" src/ | grep -o "class [A-Za-z]*" | sort | uniq -dCommon Errors:
// From platform-interfaces.tsexport class SchemaGenerationError extends PlatformError { code = 'SCHEMA_GENERATION_ERROR';}
// Common causes:// - Duplicate type names// - Circular type references// - Invalid GraphQL type definitions// - Dots in type or field namesSolution:
A. Remove Dots from Names
// ❌ WRONG: Dots in class names@Contract()export class User.CreateCommand { }
@Contract()export class User.Profile { }
// ✓ CORRECT: Use PascalCase without dots@Contract()export class CreateUserCommand { }
@Contract()export class UserProfile { }B. Fix Duplicate Type Names
// ❌ WRONG: Duplicate type name across services@Contract()export class Profile { }
// profile-service/contracts.ts@Contract()export class Profile { } // Duplicate!
// ✓ CORRECT: Unique type names// user-service/contracts.ts@Contract()export class UserProfile { }
// profile-service/contracts.ts@Contract()export class ProfileDetails { }C. Avoid Circular References
// ❌ WRONG: Circular reference@Contract()export class User { @Field() posts!: Post[];}
@Contract()export class Post { @Field() author!: User; // Circular!}
// ✓ CORRECT: Use IDs instead@Contract()export class User { @Field() @IsString() userId!: string;
@Field({ type: () => [String] }) postIds!: string[];}
@Contract()export class Post { @Field() @IsString() postId!: string;
@Field() @IsString() authorId!: string; // Reference by ID}5. Version Compatibility Issues
Section titled “5. Version Compatibility Issues”Symptoms:
- Contract version mismatches
- Breaking changes not detected
- Migration errors
- “Invalid semantic version” errors
Diagnostic Steps:
# Check contract versionscurl http://localhost:3001/api/services/my-service/contracts | jq '.version'
# View version historycurl http://localhost:3001/api/services/my-service/contracts/history | jq
# Check for breaking changesdocker logs my-service 2>&1 | grep "Breaking change"Common Errors:
// From contract-versioning.tsthrow new Error(`Invalid semantic version: ${version}`); // Line 77throw new Error(`Evolutions array not found for contract type: ${contractType}`); // Line 167throw new Error( `Cannot evolve contract from version ${fromVersion} to ${toVersion}: No migration path found`); // Line 205throw new Error(`Invalid version requirement: ${requirement}`); // Line 335Solution:
A. Use Semantic Versioning
// Contract version format: MAJOR.MINOR.PATCH// - MAJOR: Breaking changes// - MINOR: New features (backward compatible)// - PATCH: Bug fixes (backward compatible)
@Contract({ version: '1.0.0' })export class CreateUserCommand { @Field() email!: string;}
// Adding optional field = MINOR version bump@Contract({ version: '1.1.0' })export class CreateUserCommand { @Field() email!: string;
@Field() @IsOptional() name?: string; // New optional field}
// Removing field = MAJOR version bump@Contract({ version: '2.0.0' })export class CreateUserCommand { @Field() email!: string; // name removed - breaking change!}B. Define Contract Evolutions
import { ContractEvolution } from '@banyanai/platform-contract-system';
// Define migration from v1 to v2const createUserEvolution: ContractEvolution = { fromVersion: '1.0.0', toVersion: '2.0.0', migrate: (data: any) => { // Transform old format to new format return { email: data.email, // name removed in v2 }; }};
// Register evolutionContractVersionManager.registerEvolution('CreateUserCommand', createUserEvolution);C. Check Version Compatibility
import { ContractVersionManager } from '@banyanai/platform-contract-system';
// Check if version satisfies requirementconst compatible = ContractVersionManager.satisfies('1.5.0', '^1.0.0');// true - minor/patch updates compatible
const breaking = ContractVersionManager.satisfies('2.0.0', '^1.0.0');// false - major version change breaks compatibility6. Runtime Validation Failures
Section titled “6. Runtime Validation Failures”Symptoms:
- Valid data rejected by validators
- Inconsistent validation results
- Cache inconsistency errors
- Validation performance issues
Diagnostic Steps:
# Check validation errorsdocker logs my-service 2>&1 | grep "Validation failed"
# View validation detailsdocker logs my-service 2>&1 | grep -A5 "validation"
# Check cache statusdocker exec redis redis-cli INFO keyspaceCommon Errors:
// From runtime-validator.tsthrow new Error(`Cache inconsistency: key exists but value is undefined for ${cacheKey}`); // Lines 205, 278, 339Solution:
A. Use Proper Validation Decorators
import { Field } from '@banyanai/platform-contract-system';import { IsString, IsEmail, IsInt, Min, Max, MinLength, MaxLength, IsOptional} from 'class-validator';
@Contract()export class CreateUserCommand { @Field() @IsEmail() email!: string;
@Field() @IsString() @MinLength(8) @MaxLength(100) password!: string;
@Field() @IsInt() @Min(18) @Max(120) @IsOptional() age?: number;}B. Custom Validators
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
// Custom validator for phone numbersexport function IsPhoneNumber(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ name: 'isPhoneNumber', target: object.constructor, propertyName: propertyName, options: validationOptions, validator: { validate(value: any, args: ValidationArguments) { const phoneRegex = /^\+?[1-9]\d{1,14}$/; return typeof value === 'string' && phoneRegex.test(value); }, defaultMessage(args: ValidationArguments) { return 'Phone number must be in E.164 format'; } } }); };}
// Use custom validator@Contract()export class UpdateProfileCommand { @Field() @IsPhoneNumber() phoneNumber!: string;}C. Clear Validation Cache
# If validation cache becomes corrupteddocker exec redis redis-cli FLUSHDB
# Or clear specific patterndocker exec redis redis-cli --scan --pattern "validation:*" | xargs redis-cli DELAdvanced Diagnostics
Section titled “Advanced Diagnostics”Contract Scanner Inspection
Section titled “Contract Scanner Inspection”# Check contract discoverydocker logs my-service 2>&1 | grep "Contract scanner"
# View discovered contractsdocker logs my-service 2>&1 | grep "Discovered contract"
# Should show:# [ContractScanner] Discovered contract: CreateUserCommand# [ContractScanner] Discovered contract: UpdateUserCommandSchema Generation Debug
Section titled “Schema Generation Debug”// Enable schema generation loggingprocess.env.DEBUG = 'schema:*';
// View generated schemaimport { SchemaGenerator } from '@banyanai/platform-contract-system';
const schema = SchemaGenerator.generate({ contracts: [CreateUserCommand, UpdateUserCommand], serviceName: 'user-service'});
console.log('Generated schema:', schema);Contract Registry Inspection
Section titled “Contract Registry Inspection”# Query contract registrycurl http://localhost:3001/api/services/my-service/contracts | jq '.'
# Get specific contractcurl http://localhost:3001/api/services/my-service/contracts/CreateUser | jq '.'
# View contract versionscurl http://localhost:3001/api/services/my-service/contracts/CreateUser/versions | jq '.'Best Practices
Section titled “Best Practices”1. Always Use @Field() Decorator
Section titled “1. Always Use @Field() Decorator”// Every contract field needs @Field()@Contract()export class MyCommand { @Field() // REQUIRED @IsString() field1!: string;
@Field() // REQUIRED @IsInt() field2!: number;}2. Export All Contracts
Section titled “2. Export All Contracts”export { CreateUserCommand } from './CreateUserCommand.js';export { UpdateUserCommand } from './UpdateUserCommand.js';export { DeleteUserCommand } from './DeleteUserCommand.js';3. Use Semantic Versioning
Section titled “3. Use Semantic Versioning”// Track breaking changes with major version@Contract({ version: '1.0.0' }) // Initial@Contract({ version: '1.1.0' }) // Added optional field@Contract({ version: '2.0.0' }) // Breaking change4. Validate Permissions
Section titled “4. Validate Permissions”// Validate permission format at compile timetype Permission = `${string}:${string}`;
@RequiresPermission('users:create' as Permission)export class CreateUserHandler { }5. Document Contracts
Section titled “5. Document Contracts”@Contract({ description: 'Creates a new user account', version: '1.0.0'})export class CreateUserCommand { @Field({ description: 'User email address (must be unique)', example: 'user@example.com' }) @IsEmail() email!: string;
@Field({ description: 'User password (minimum 8 characters)', example: 'SecurePass123!' }) @IsString() @MinLength(8) password!: string;}Related Documentation
Section titled “Related Documentation”Summary
Section titled “Summary”Most contract system issues are caused by:
- Missing @Field() decorators - Every field needs @Field()
- Invalid permission format - Use
resource:action(lowercase, colon-separated) - Dots in names - Remove dots from class and field names
- Missing validators - Add class-validator decorators
- Export issues - Export all contract classes
Use contract validation tools and schema inspection to diagnose issues quickly.