3. Facets and Entities
An entity is a shared identity composed from facets. A facet owns a coherent slice of state and behavior.
ops.area
├── identity code, name, description
├── tenancy tenant, site
├── governance status, actions, audit
└── hierarchy type, category, parent, capacityBlueprint deliberately does not put lifecycle or actions directly on Entity. Different facets can carry different state machines, rules, evidence requirements, and action surfaces.
Declare the entity
final Entity areaEntity = Entity(
name: 'area',
title: 'Area',
pluralTitle: 'Areas',
description: 'Controlled physical spaces within an operating site.',
tableName: 'areas',
schema: 'ops',
schemaType: 'ops.area',
tenantField: AreaFields.tenantId,
versioning: const EntityVersioning.revisions(),
uniqueKeys: const [
[AreaFields.tenantId, AreaFields.code],
],
facets: const [
IdentityFacet(
fields: [
AreaFields.code,
AreaFields.name,
AreaFields.description,
],
),
Facet(
name: 'tenancy',
fields: [AreaFields.tenantId, AreaFields.siteId],
),
Facet(
name: 'hierarchy',
title: 'Hierarchy',
fields: [
AreaFields.areaType,
AreaFields.capacity,
AreaFields.roomNumber,
],
),
],
);Identity is explicit
Every assembled entity requires exactly one IdentityFacet. Composite unique keys must use the same field token objects declared on that identity surface. Tenant-scoped uniqueness is explicit:
uniqueKeys: const [
[AreaFields.tenantId, AreaFields.code],
],There is no hidden tenant prepend.
Facet dependencies form a DAG
When one facet requires another:
Facet(
name: 'release',
dependsOn: const ['governance', 'quality_review'],
// ...
)The validator rejects missing dependencies and cycles. Dependencies describe semantic ordering; they do not mean “render this below that.” UI order belongs in FacetUI.
Versioned entities retain definitions
Use EntityVersioning.versions for immutable point-in-time history and const EntityVersioning.revisions() when the entity also needs governed business revisions, approval context, evidence links, and producing actions. Both use the same monotonically increasing technical version sequence. An execution record can then pin the exact version it used.
Version history is not a replacement for the action journal. It is a state projection that makes historical definitions resolvable.
Projections and aggregates
FacetProjectiondescribes the facet's read/write projection.EntityProjectionis assembled from effective facets.DerivedValuebelongs to a facet when it depends on that facet.- Entity
aggregatescombine multiple facets.
Stored fields remain stored state. Do not create fake persisted fields for values that are derived from other state.
Validate early
final blueprint = Blueprint.single(
name: 'learning_ops',
version: '0.1.0',
schema: 'ops',
entities: [areaEntity],
);
final errors = BlueprintValidator.validate(blueprint);
if (errors.isNotEmpty) {
for (final error in errors) {
print('${error.code} @ ${error.path}: ${error.message}');
}
}Validation is the language type-checking phase. Run it before DDL generation, server bootstrap, or UI assembly.
Checkpoint
Why is governance a facet rather than an entity-level boolean called active? Your answer should mention lifecycle, rules, actions, evidence, and audit.