{"record":{"id":"0aa5e95b3ac600ca","repo":"ruvnet/ruflo","slug":"event-must-have-a-valid-type-string","errorCode":null,"errorMessage":"Event must have a valid type string","messagePattern":"Event must have a valid type string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/shared/src/events/rvf-event-log.ts","lineNumber":166,"sourceCode":"    this.snapshots.clear();\n    this.initialized = false;\n\n    this.emit('shutdown');\n  }\n\n  // ===========================================================================\n  // Write Operations\n  // ===========================================================================\n\n  /** Append a domain event to the log. */\n  async append(event: DomainEvent): Promise<void> {\n    this.ensureInitialized();\n\n    if (!event.aggregateId || typeof event.aggregateId !== 'string') {\n      throw new Error('Event must have a valid aggregateId string');\n    }\n    if (!event.type || typeof event.type !== 'string') {\n      throw new Error('Event must have a valid type string');\n    }\n\n    // Assign next version for aggregate\n    const currentVersion = this.aggregateVersions.get(event.aggregateId) ?? 0;\n    const nextVersion = currentVersion + 1;\n    event.version = nextVersion;\n\n    // Persist to disk first (crash-safe ordering)\n    this.appendRecord(this.config.logPath, event);\n\n    // Update in-memory state\n    this.indexEvent(event);\n\n    this.emit('event:appended', event);\n\n    if (nextVersion % this.config.snapshotThreshold === 0) {\n      this.emit('snapshot:recommended', {\n        aggregateId: event.aggregateId,","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/shared/src/events/rvf-event-log.ts#L148-L184","documentation":"As the second guard in RvfEventLog.append(), the event's type field must be a non-empty string before the event is versioned and persisted. The type drives dispatch during replay, so a missing, blank, or non-string type is rejected up front. This typically means the event was constructed without a discriminator or with a renamed field.","triggerScenarios":"append({ aggregateId, payload }) with no type; type: '' or type: undefined; type passed as a symbol/number; spread construction that overwrites or drops type.","commonSituations":"Factory helpers defaulting type only for some event kinds; refactors renaming event type constants; deserialization dropping the field; payloads mistakenly passed as the whole event.","solutions":["Always set type from a shared constant or enum at the append site","Check the event shape with a type guard before append() (covers aggregateId and type together)","After renames, grep for old type constant names to catch stale producers"],"exampleFix":"// before\nawait log.append({ aggregateId: id } as DomainEvent); // throws: invalid type\n\n// after\nawait log.append({ aggregateId: id, type: 'order.created' } as DomainEvent);","handlingStrategy":"type-guard","validationCode":"if (typeof event.type !== 'string' || event.type.length === 0) {\n  throw new TypeError('DomainEvent.type must be a non-empty string');\n}\nawait log.append(event);","typeGuard":"function hasEventType(e: unknown): e is DomainEvent {\n  return !!e && typeof e === 'object'\n    && typeof (e as any).type === 'string' && (e as any).type.length > 0\n    && typeof (e as any).aggregateId === 'string' && (e as any).aggregateId.length > 0;\n}","tryCatchPattern":"try {\n  await log.append(event);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('valid type string')) {\n    // producer sent an event without a type discriminator: fix upstream\n  } else throw e;\n}","preventionTips":["Define event types as a const/enum and require them in the event factory","Guard deserialized events with hasEventType() before append","After renaming type constants, sweep for stale producers"],"tags":["event-sourcing","validation","event-type","event-log"],"backgroundTag":"schema-validation-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}