Skip to content

Entity Descriptors

Entity descriptors make Blueprint a layered module language for regulated software. A module can define the base entity type, while other modules contribute actional facets to that same type.

Vocabulary

  • Entity: the identity-bearing business object and facet composition boundary, such as manufacturing.product.
  • Facet: one actional dimension of an entity. A facet may carry fields, relationships, actions, lifecycle, policies, evidence requirements, audit posture, state projection, derived values, subscriptions, and UI hints.
  • Entity descriptor: a module contribution to an existing entity type. Descriptors contribute facets; they do not create a competing entity and they do not add entity-level actions.
  • Bootstrapped blueprint: the effective graph produced by applying all module descriptors to their target entity types.

Stable Entity Types

Every entity resolves to a stable schema type. If schemaType is not supplied, Blueprint uses:

text
<module.name>.<entity.name>

For example:

text
manufacturing.product
manufacturing.equipment
manufacturing.area

CVS and ELog should not define cvs.product or elog.product when they mean the same business object. They contribute descriptors to manufacturing.product.

Descriptor Assembly

text
manufacturing.product
  identity
  tenancy
  governance
  composition

cvs descriptor -> manufacturing.product
  cvs_cleaning_profile
  governance additions

elog descriptor -> manufacturing.product
  elog_execution_history

After blueprint.bootstrap(), the effective entity is:

text
manufacturing.product
  identity
  tenancy
  governance
  composition
  cvs_cleaning_profile
  elog_execution_history

If a descriptor contributes a facet whose name already exists, the facet is merged. This is how governance, tenancy, assignment, and traceability can become shared regulated surfaces while still accepting domain-specific requirements.

All behavior contributed by a descriptor is facet-owned. If a module needs to add an action that appears globally for the entity, it should contribute that action to the identity facet or to the facet that best represents the transaction's intent.

Blueprint exposes this through bootstrap:

dart
final bootstrapped = blueprint.bootstrap();
final product = bootstrapped.entity('manufacturing.product');

Compilers consume the same bootstrapped model:

dart
final entities = blueprint.bootstrap().assembledEntities;

Origin Graph

Every bootstrapped entity carries an originGraph. The graph records where each effective Blueprint node came from: base module, contributing module, aspect, parent path, facet, and member name.

dart
final graph = product!.originGraph;
final origins = graph.originsOf(
  'manufacturing.product.facets.cvs_cleaning_profile.fields.worst_case_rank',
);

The bootstrapped blueprint also exposes a graph across module-level atoms and effective entities:

dart
final graph = bootstrapped.originGraph;
final moduleOrigins = graph.originsOf(
  'blueprints.manufacturing_apps.modules.cvs',
);

The origin graph is intentionally deeper than fields and actions. Every runtime-shaping Blueprint object is traceable to the module/aspect that contributed it, including:

  • blueprint, modules, module DB hints, module UI hints, module seed hints, module exports, module policies, and entity descriptors
  • entity, entity DB hints, entity UI hints, and entity seed hints
  • aggregates and aggregate dependencies
  • facets, facet dependencies, and facet UI hints
  • fields, field types, field DB hints, field UI hints, and field seed hints
  • relationships
  • lifecycle, lifecycle state field, initial state, transitions, transition rules, effects, effect targets, and effect payload mappings
  • facet actions, action payload fields, rules, UI hints, audit, evidence, evidence source, and evidence retention
  • facet projections, projected fields, storage bindings, query behavior, and UI behavior
  • derived values, derived value dependencies, evaluator refs, SQL bodies, and declared policy reads
  • subscriptions and subscription conditions
  • policies, evaluator refs, config, and declared reads
  • facet-level evidence, audit, and audit evidence

This makes it possible to draw manufacturing lineage at any level: which module contributed the facet, which aspect contributed a field, which lifecycle transition came from CVS, which DB index hint came from ELog, or which evidence requirement was layered by a governance module.

Lifecycle And Actions

Lifecycle and actions belong inside facets. They are not entity-level concepts.

An entity can have many actional state dimensions:

text
equipment
  availability lifecycle
  cleaning lifecycle
  calibration lifecycle
  maintenance lifecycle

Blueprint rejects multiple lifecycle contributors for the same facet because the state machine needs one clear owner.

Tenancy And Governance

Tenancy is a core facet that declares ownership, scope, applicability, and isolation. Runtime action context still carries active tenant/site scope, and policies compare that context with the entity's tenancy facet.

Governance is a core facet for regulated posture: audit strictness, evidence requirements, signature requirements, reason-code requirements, retention, review cadence, and exception posture. Actual audit records are produced by the runtime when actions execute.

Module Rules

A descriptor can target:

  • an entity in its own module, or
  • an entity exported by a dependency module.

For cross-module contributions, the source module must declare dependsOn and the target module must export the entity with EntityExport.

This keeps descriptor contribution open for layering, but still governed at module boundaries.

State Projection and Storage

Compilers consume blueprint.bootstrap().assembledEntities, not raw base entities. That means descriptor-contributed facets participate in the same DB/API/UI pipeline as base facets.

Facets own state, so facets also own FacetProjection. The entity state model is the union of all facet projections on the effective entity.

The current Postgres projection is relational by default:

  • the identity facet becomes the host table, e.g. manufacturing.products
  • each non-identity facet becomes a facet table, e.g. manufacturing.products_cvs_cleaning_profile
  • each facet table uses entity_id as its primary key and FK to the host table
  • facet fields become typed columns
  • lifecycle state fields become typed columns on the facet table
  • derived values become generated columns, views, materialized views, or server evaluated read fields
  • audit records are written to per-facet audit tables
  • the read model joins identity plus all facet tables for API and UI state

Performance is controlled through DB hints:

  • FieldDb(indexed: true) emits indexes
  • FieldDb(indexKind: DbIndexKind.gin) supports JSONB/search-heavy fields
  • FieldDb(queryPatterns: [...]) documents filter/sort/search/aggregate usage
  • EntityDb declares expected rows, write pressure, partitioning, retention, audit mode, and realtime posture
  • ModuleDb owns schema-level RLS, outbox, realtime, cross-entity indexes, views, and triggers

Client-side state should not expose the storage split. The read/API compiler uses the assembled entity and EntityStateProjection so app code can interact with a simple entity object while the DB remains normalized and indexable.

Blue is the Vyuh Blueprint documentation surface.