FabricFabricPlatform
Platform referenceEntities

Entity constraints and invariants

The constraints the platform enforces about ontology shapes, state machines, and registration.

This page is a single-page reference for the rules the runtime enforces about the entities you register. Most are checked at registration time so misconfiguration fails at startup, not in production.

Identifier formats

IdentifierRegexExample
Module namespace^[a-z][a-z0-9-]*$lending, borrower-portal
Object type^[A-Z][a-zA-Z0-9]*$Offer, Vehicle
Action ID^[a-z][a-z0-9-]*\.[a-z][a-z0-9_]*$lending.accept_offer
Policy ID^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*\.v[0-9]+$lending.credit_pull_consent.v1
Fabric ID^[a-z][a-z0-9]{1,7}_[0-9A-Z]{26}$act_01HYZAB…

Action invariants

Enforced at registerAction:

  1. Action IDs are unique. Re-registering the same ID throws. Use registerActionIfMissing for idempotent boot paths.
  2. Mutating actions emit events. mutatesDomain: true && emitsEvents.length === 0 throws.
  3. Action namespace prefix. When registered through registerFabricModule, the action ID must start with <module.namespace>..

Policy invariants

Enforced at registerPolicy:

  1. Policy ID format. Must end with .v<integer>.
  2. No conflicting registrations. Registering a different evaluator for the same ID throws.
  3. Same evaluator re-registration is a no-op. Idempotent re-registration of the same evaluator object is safe.

Enforced at runtime by evaluateDataPolicyDefinition:

BoundLimit
Maximum tree depth5
Maximum conditions per node20
Maximum total conditions in a policy100
Maximum path segments12
Maximum string literal length500
Allowlisted context pathstenantId, spaceId, actionInvocationId, actionId, mode

These bounds prevent accidentally deploying a policy that takes seconds to evaluate.

State-machine invariants

Enforced by validateTransition:

  1. The entity type must have a registered state machine.
  2. Both from and to must be declared states.
  3. The (from, to, actionId) triple must be a declared transition.

Stricter than typical state-machine libraries: the action causing the transition is part of the rule. OfferState.presented → accepted via lending.accept_offer is allowed; the same transition via lending.expire_stale_offer is not.

Module invariants

Enforced at registerFabricModules:

  1. No duplicate namespaces.
  2. No self-dependency.
  3. No cyclic dependencies.
  4. All declared dependencies must resolve to a registered module (or already be registered).
  5. Display renderers must reference event types known to the platform or declared by the same/another module in the batch.

Event invariants

  1. Event types are registered before use. Emitting an unknown event type fails (verified by the event-append API).
  2. Per-subject sequences are monotonic. Replay engines flag gaps via missing_sequence warnings.
  3. Event IDs are unique. Replay deduplicates; storage layer enforces.

Adapter invariants

  1. Adapter (adapterType, operation) pairs are unique in the registry. Re-registration throws.
  2. Non-idempotent adapters cannot retry. The runtime forces maxAttempts: 1 regardless of policy configuration.
  3. Retry cap. Idempotent adapters are capped at maxAttempts: 5 even if a higher number is configured.

Where these are tested

  • packages/platform/boundary.test.ts — vertical-vocabulary leakage into the platform.
  • packages/platform/modules/*.test.ts — manifest validation.
  • packages/api/modules/{your-application,borrower-portal}/boundary.test.ts — direct-mutation and direct-handler-import bans.
  • packages/adapters/boundary.test.ts — vertical imports in adapters.

See also

On this page