Entity Descriptors
Entity descriptors make Blueprint a layered module language for regulated software. Modules contribute descriptors; descriptors contribute facets; the assembler combines those facets into the final entity.
Vocabulary
- Entity descriptor: a module contribution to an entity type. Descriptors have a
target, anaspect, and facets. - Entity: the assembled 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, projection, derived values, subscriptions, and UI hints.
- Effective blueprint: the effective graph produced by applying all module descriptors to their target entity types.
Stable Entity Types
Every entity resolves to a stable type, its module and name:
<module.name>.<entity.name>For example:
manufacturing.product
manufacturing.equipment
manufacturing.areaA cleaning module and a production module should not define cleaning.product or production.product when they mean the same business object. They contribute descriptors to manufacturing.product.
Descriptor Assembly
manufacturing.product
identity
tenancy
governance
composition
cleaning descriptor -> manufacturing.product
cleaning_profile
governance additions
production descriptor -> manufacturing.product
batch_historyAfter blueprint.assemble(), the effective entity is:
manufacturing.product
identity
tenancy
governance
composition
cleaning_profile
batch_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. IdentityFacet is the exception: every assembled entity must have exactly one identity contributor. A module may introduce the IdentityFacet, or a descriptor may, but two identity contributors are invalid. A generic Facet(name: 'identity') is rejected.
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 IdentityFacet or to the facet that best represents the transaction's intent.
Descriptors can also describe UI surface posture through EntityUIOverride. This covers entity icon tokens, categories, route/list visibility, menu grouping, table columns, default sort, and read-only posture. The descriptor is assembled into Entity.ui, so App Blueprint and Blueprint UI can harvest the final registry without needing app descriptors for entity-owned surfaces.
Only one descriptor can own unique UI posture such as icon, category, route prefix, access, visibility, read-only posture, or default sort. If two descriptors disagree, validation reports blueprint.validation.entity_ui_descriptor_conflict. Table columns are additive and deduped during assembly.
Blueprint exposes this through assembly:
final effective = blueprint.assemble();
final product = effective.entity('manufacturing.product');Compilers consume the same effective model:
final entities = blueprint.assemble().assembledEntities;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 request 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 dependencies and the target module must export the entity with EntityExport.
This keeps descriptor contribution open for layering, but still governed at module boundaries.