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:
<module.name>.<entity.name>For example:
manufacturing.product
manufacturing.equipment
manufacturing.areaCVS 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
manufacturing.product
identity
tenancy
governance
composition
cvs descriptor -> manufacturing.product
cvs_cleaning_profile
governance additions
elog descriptor -> manufacturing.product
elog_execution_historyAfter blueprint.bootstrap(), the effective entity is:
manufacturing.product
identity
tenancy
governance
composition
cvs_cleaning_profile
elog_execution_historyIf 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:
final bootstrapped = blueprint.bootstrap();
final product = bootstrapped.entity('manufacturing.product');Compilers consume the same bootstrapped model:
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.
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:
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:
equipment
availability lifecycle
cleaning lifecycle
calibration lifecycle
maintenance lifecycleBlueprint 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_idas 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 indexesFieldDb(indexKind: DbIndexKind.gin)supports JSONB/search-heavy fieldsFieldDb(queryPatterns: [...])documents filter/sort/search/aggregate usageEntityDbdeclares expected rows, write pressure, partitioning, retention, audit mode, and realtime postureModuleDbowns 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.