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;
}
public interface IAssetEventEnvelope<TPayload>
{
    string Id { get; }                    // evt_<ULID>
    string TenantId { get; }
    string SpaceId { get; }
    string EventType { get; }             // PascalCase past-tense
    int EventSchemaVersion { get; }       // integer per event type
    string SubjectType { get; }           // PascalCase, must be registered
    string SubjectId { get; }
    string ActorId { get; }
    string ActorType { get; }             // see ActorType enum
    string? ActionInvocationId { get; }   // empty for platform-emitted events not tied to an action
    TPayload Payload { get; }
    int Sequence { get; }                 // monotonic per (subjectType, subjectId)
    DateTime OccurredAt { get; }
    DateTime RecordedAt { get; }
    string CorrelationId { get; }
    string? CausationId { get; }
}

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;
public static class SubjectTypeRegistry
{
    public static void Register(string type);
    public static bool IsValid(string type);
    public static IReadOnlyList<string> GetRegistered();
}

public static class EventTypeRegistry
{
    public static void Register(string type);
    public static bool IsValid(string type);
    public static IReadOnlyList<string> GetRegistered();
}

public interface IEventTypeRegistry
{
    void Register(string eventType);
    bool Has(string eventType);
    IReadOnlyList<string> List();
}

public static IEventTypeRegistry CreateEventTypeRegistry(IEnumerable<string>? initial = null);

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 (PlanAccepted). Never imperative (AcceptPlan).
  • Subject types — PascalCase noun (Plan, Order).
  • Action IDs<namespace>.<snake_verb> (orders.submit).
  • Policy IDs<id>.v<integer> (orders.payment_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