FabricFabricPlatform
Platform referenceReference

Event schema reference

Full envelope, registration API, and naming rules for AssetEvents.

AssetEventEnvelope

interface AssetEventEnvelope<TPayload = unknown> {
  id: string;                    // evt_<ULID>
  tenantId: string;
  spaceId: string;
  eventType: string;             // PascalCase past-tense
  eventSchemaVersion: number;    // integer per event type
  subjectType: string;           // PascalCase, must be registered
  subjectId: string;
  actorId: string;
  actorType: string;             // see ActorType enum
  actionInvocationId?: string;   // empty for platform-emitted events not tied to an action
  payload: TPayload;
  sequence: number;              // monotonic per (subjectType, subjectId)
  occurredAt: Date;
  recordedAt: Date;
  correlationId: string;
  causationId?: string;
}

Field semantics

FieldRequiredNotes
idyesSortable Crockford-ULID with evt_ prefix.
tenantId, spaceIdyesReplay scope dimensions. Filtered first.
eventTypeyesMust be in the event type registry.
eventSchemaVersionyesBump when payload shape changes. Old events still replay against old shape.
subjectTypeyesMust be in the subject type registry.
subjectIdyesThe entity this event is about.
actorId, actorTypeyesWho/what caused the event.
actionInvocationIdnoPresent for events caused by a handler. Absent for some platform events.
sequenceyesPer-subject. Use to detect gaps and order replay.
occurredAtyesDomain time. May differ from recordedAt for backfills.
recordedAtyesPlatform-stamped time of append.
correlationIdyesStable across all events of a related invocation chain.
causationIdnoDirect parent invocation. Forms a tree for sagas.

Registration API

From packages/platform/events/index.ts:

export function registerSubjectType(type: string): void;
export function isValidSubjectType(type: string): boolean;
export function getRegisteredSubjectTypes(): string[];

export function registerEventType(type: string): void;
export function isValidEventType(type: string): boolean;
export function getRegisteredEventTypes(): string[];

export interface EventTypeRegistry {
  register(eventType: string): void;
  has(eventType: string): boolean;
  list(): string[];
}

export function createEventTypeRegistry(initial?: string[]): EventTypeRegistry;

Verticals do not call these directly — they declare event types in their FabricModule.eventTypes, and registerFabricModule calls the registration functions for them.

Pre-registered subject types

Subject type
ActionInvocation
PolicyEvaluation
AdapterInvocation

Pre-registered event types

Event typeDescription
ComplianceBlockedA policy returned block.
StateTransitionedA state-machine transition was applied during a handler.
AdapterInvocationStartedAdapter step beginning.
AdapterInvocationSucceededAdapter step success.
AdapterInvocationFailedAdapter step terminal failure.
WebhookReceivedWebhook captured by integration adapter.

Naming rules

Enforced by convention; tooling rejects in code review:

  • Event types — PascalCase, past tense fact (OfferAccepted). Never imperative (AcceptOffer).
  • Subject types — PascalCase noun (Offer, Vehicle).
  • Action IDs<namespace>.<snake_verb> (lending.accept_offer).
  • Policy IDs<id>.v<integer> (lending.credit_pull_consent.v1).

Schema versioning rules

When a payload shape changes:

  1. Bump eventSchemaVersion for the affected event type.
  2. Add a new schema for the new version. Keep the old schema accessible.
  3. Projection reducers that consume both versions branch on event.eventSchemaVersion.
  4. Never edit past events. The platform replays them as-is.

Common safe changes that don't require a bump:

  • Adding optional fields with sensible default behavior in reducers.

Changes that do require a bump:

  • Removing a field.
  • Renaming a field.
  • Changing a field type.
  • Adding a required field.

ID format

^[a-z][a-z0-9]{1,7}_[0-9A-Z]{26}$

The 26-character suffix is a Crockford-base32 ULID (10 chars time + 16 chars random). The evt_ prefix is fixed for events.

See also

On this page