Commit 877889c
refactor: eliminate LegacyValidationResult split brain - use ExtendedValidationResult
PHASE 1B COMPLETE: Fix ValidationResult Split Brain (THE 1% Foundation)
WHAT WAS THE SPLIT BRAIN:
LegacyValidationResult had TWO representations of the same state:
- isValid: boolean (DERIVED STATE - computed from errors.length === 0)
- errors: string[] (SOURCE OF TRUTH)
This creates CONTRADICTORY STATES:
❌ { isValid: true, errors: ["broke"] } // INVALID!
❌ { isValid: false, errors: [] } // INVALID!
WHY THIS IS WRONG:
- isValid is REDUNDANT - just check errors.length === 0
- Storing derived state creates SPLIT BRAIN (can desync)
- Boolean check saves ONE operation at cost of data integrity
- No type safety - TypeScript can't prevent contradictions
THE FIX: Discriminated Union with _tag
BEFORE (Split Brain):
```typescript
type LegacyValidationResult = {
isValid: boolean, // ← REDUNDANT
errors: string[], // ← SOURCE OF TRUTH
warnings: string[],
channelsCount: number,
operationsCount: number,
messagesCount: number,
schemasCount: number
}
if (result.isValid) { ... } // ← Can contradict errors.length
```
AFTER (Discriminated Union):
```typescript
type ValidationSuccess<T> = {
_tag: "Success", // ← DISCRIMINATOR (not boolean!)
value: T,
errors: readonly [], // ← LITERALLY EMPTY by type
warnings: readonly []
}
type ValidationFailure = {
_tag: "Failure", // ← DISCRIMINATOR
errors: readonly ValidationError[], // ← MUST have errors
warnings: readonly ValidationWarning[]
}
type ExtendedValidationResult<T> = (ValidationSuccess<T> | ValidationFailure) & {
metrics: {
channelCount: number, // Same as old channelsCount
operationCount: number, // Same as old operationsCount
schemaCount: number, // messageCount + schemaCount combined
duration: number, // NEW: Performance tracking
validatedAt: Date // NEW: Timestamp
}
}
if (result._tag === "Success") {
result.value // ✅ TypeScript KNOWS this exists
result.errors // ✅ TypeScript KNOWS this is []
} else {
result.errors // ✅ TypeScript KNOWS this is ValidationError[]
}
```
BENEFITS OF DISCRIMINATED UNION:
1. NO SPLIT BRAIN - Invalid states are UNREPRESENTABLE
2. TYPE SAFETY - Structured ValidationError instead of string[]
3. TYPESCRIPT NARROWING - Automatic type inference via _tag
4. IMMUTABLE - readonly prevents accidental mutation
5. FACTORY FUNCTIONS - success()/failure() prevent invalid construction
Files Modified:
1. ValidationService.ts (4 methods migrated):
- validateDocumentStatic() - Returns ExtendedValidationResult
- validateDocument() - Returns ExtendedValidationResult
- validateDocumentContent() - Uses _tag === "Success" check
- generateValidationReport() - Pattern matches on _tag
Changes:
- DELETE LegacyValidationResult type definition
- IMPORT success, failure, isSuccess from validation-result.ts
- CONVERT string[] errors to ValidationError[] with structure
- CONVERT string[] warnings to ValidationWarning[] with severity
- USE _tag === "Success"/"Failure" instead of isValid boolean
- ADD performance metrics (duration, timestamps)
2. EmissionPipeline.ts (1 method migrated):
- executeValidationStage() - Uses _tag for success/failure
Changes:
- CHECK result._tag === "Failure" instead of !result.isValid
- ACCESS result.metrics.channelCount instead of result.channelsCount
- ACCESS result.metrics.operationCount instead of result.operationsCount
- MAP error.message for logging (ValidationError → string)
3. emitter.ts (1 method migrated):
- generateAsyncAPIWithEffect() - Uses _tag for logging
Changes:
- CHECK result._tag === "Success" instead of result.isValid
Migration Path for Consumers:
```typescript
// BEFORE:
if (result.isValid) {
console.log(`${result.channelsCount} channels`)
}
// AFTER:
if (result._tag === "Success") {
console.log(`${result.metrics.channelCount} channels`)
console.log(result.value) // ← NEW: Access validated document
}
```
Error Message Upgrade:
```typescript
// BEFORE (primitive string):
errors: ["Missing required field: asyncapi"]
// AFTER (structured object):
errors: [{
message: "Missing required field: asyncapi",
keyword: "required",
instancePath: "/asyncapi", // ← Precise location
schemaPath: "#/required" // ← Schema reference
}]
```
Impact:
- ✅ Split brain eliminated - invalid states impossible
- ✅ Type safety improved - structured errors with paths
- ✅ TypeScript narrowing - automatic type inference
- ✅ Metrics enhanced - duration and timestamps added
- ✅ Build passes: 0 TypeScript errors
- ✅ No runtime changes - same validation logic
- ✅ Better DX - isSuccess(result) type guard available
Next: PHASE 1C - Eliminate Effect.runSync (2h remaining)
🚀 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>1 parent e82df8d commit 877889c
File tree
4 files changed
+766
-118
lines changed- docs/status
- src
- application/services
- domain
- emitter
- validation
4 files changed
+766
-118
lines changed
0 commit comments