Blueprint Core Language
This document specifies the declaration and linking portion of the current Blueprint language.
1. Abstract syntax
The following grammar describes semantic containment. * means zero or more, + means one or more, and ? means optional. It is not a second textual syntax.
Blueprint ::= Identity Module+ ApplicationSurface*
Module ::= Identity Entity* ModuleInvariant* TaskTemplate*
ErrorDefinition* RemedyDefinition* Descriptor*
Export* Dependency*
Entity ::= Identity IdentityFacet Facet* EntityInvariant*
NamedQuery* Versioning Projection DbHints UIHints
Facet ::= Identity Field* Relationship* Lifecycle? Action*
DerivedValue* Subscription* Evidence* AuditEnvelope
Lifecycle ::= InitialState State+ Transition*
Action ::= Identity PayloadField* PayloadShape Consistency Rule*
Evidence* AuditEnvelope SnapshotTarget* ErrorRef*
Event* Effect* CapturePolicy
Relationship ::= Identity Target Cardinality StorageBinding Invariant*
Projection ::= ProjectedField* Access QueryBehavior UIBehaviorIdentity consists of stable machine identity and optional human-facing metadata. Human titles and descriptions MUST NOT be used to resolve semantic references.
An Entity MUST contain exactly one IdentityFacet. Entity identity is shared by all facets; state, actions, evidence, relationships, and lifecycle remain facet-owned.
2. Dart concrete form
Blueprint uses ordinary Dart syntax and the public constructors exported by package:vyuh_blueprint/vyuh_blueprint.dart. The concrete language therefore inherits Dart's imports, constants, variables, functions, type checking, collections, and control flow.
The Blueprint-specific constructor shape is:
blueprint-declaration ::= Blueprint(
name: string,
version: string,
modules: list<module-declaration>,
application-properties?
)
module-declaration ::= Module(
name: string,
version: string,
entities: list<entity-declaration>,
module-properties?
)
entity-declaration ::= Entity(
name: string,
facets: list<facet-declaration>,
entity-properties?
)
facet-declaration ::= IdentityFacet(facet-properties?)
| Facet(name: string, facet-properties?)The notation above documents the constructors; authors write Dart, not the notation.
Typed declaration example
import 'package:vyuh_blueprint/vyuh_blueprint.dart';
enum EquipmentState { draft, active, retired }
abstract final class EquipmentFields {
static const code = CodeField();
static const name = NameField();
static const state = StatusField(
'state',
EquipmentState.values,
dbEnumName: 'equipment_state',
);
}
final equipmentBlueprint = Blueprint.single(
name: 'asset',
version: '1.0.0',
schema: 'asset',
entities: [
Entity(
name: 'equipment',
facets: [
IdentityFacet(
fields: [
EquipmentFields.code,
EquipmentFields.name,
EquipmentFields.state,
],
),
],
),
],
);Field objects are both declarations and typed tokens. Local references SHOULD reuse those objects instead of repeating their names as strings. Strings remain valid at explicit boundaries such as stable names, external identifiers, extension refs, SQL expressions, paths, and wire keys.
3. Names and references
The semantic namespace is module-owned:
module asset
entity equipment
qualified entity asset.equipment
facet identity
qualified action asset.equipment.identity.updateModuleRef, EntityRef, FacetRef, ActionRef, RelationshipRef, NamedQueryRef, and the other Ref types are declaration references. Database schema and table names are physical derivatives and MUST NOT replace semantic identity.
Cross-module references MUST satisfy both conditions:
- the consuming module declares the source module in
dependsOn; and - the source module exports the referenced entity, derived value, or action.
Module dependencies and facet dependencies MUST form directed acyclic graphs.
4. Static semantics
BlueprintValidator.validate(blueprint) is the language's structural type and integrity checker. It returns all BlueprintValidationError values; an empty list means that the declaration is well formed.
The checker enforces, among other rules:
- unique module, entity, facet, field, relationship, action, projection, invariant, evidence, task-template, error, and remedy identities;
- exactly one typed identity facet per entity;
- valid lifecycle states and deterministic transitions;
- typed enum declarations and append-only enum evolution;
- valid field defaults, identifiers, measured-field companions, related-field chains, and composite unique keys;
- resolvable relationship storage and cross-module export gates;
- resolvable effect payload and workflow input mappings;
- acyclic derivation and propagation graphs;
- database-enforceable invariant placement; and
- valid UI derivations, action placements, and navigation categories.
Application-surface validation is performed by BlueprintApplicationValidator after the entity graph is bootstrapped. Runtime readiness checks complement static validation; they do not weaken it.
5. Diagnostics
A static diagnostic has three stable parts:
code blueprint.validation.<identifier>
path dotted location inside the Blueprint
message human-readable reasonTools MUST use code for programmatic handling, SHOULD use path to focus the offending declaration, and MAY render message directly.
Representative diagnostics include:
| Code | Meaning |
|---|---|
blueprint.validation.identity_facet_missing | An entity has no typed identity facet. |
blueprint.validation.duplicate_field | Two effective fields have the same identity in one facet. |
blueprint.validation.module_cycle | Module dependencies are cyclic. |
blueprint.validation.cross_module_entity_not_exported | A consumer reaches an entity the owner did not export. |
blueprint.validation.lifecycle_ambiguous_transition | More than one transition matches the same state and trigger. |
blueprint.validation.effect_payload_type_mismatch | An effect maps incompatible source and target fields. |
blueprint.validation.workflow_effect_invalid_ref | A workflow start does not use a valid typed workflow ref. |
blueprint.validation.enum_evolution_not_append_only | A persisted enum changed incompatibly. |
Validators MUST collect independent structural failures instead of failing at the first one. Compilers and servers MUST reject an invalid Blueprint before installing or executing it.
6. Linking and effective semantics
Blueprint.bootstrap() links raw declarations into an EffectiveBlueprint. Linking:
- resolves module and entity identities;
- applies descriptor contributions in deterministic order;
- merges facet, action, projection, query, and UI declarations;
- records origin information; and
- exposes the effective entities consumed by compilers and runtimes.
Downstream consumers MUST use the effective graph. Traversing raw module entities after linking would omit descriptor contributions and can produce a different program.
7. Canonical serialization
BlueprintDefinitionDocument.capture creates the canonical archive of the authored Blueprint declaration and its compiled application manifest. Its current format identifier is:
vyuh.blueprint.definition/v2The capture has an envelope, the complete definition, and an extracted reference index. The index is queryable integrity metadata; the definition document remains authoritative.
Canonicalization obeys these rules:
- map keys are converted to strings and sorted recursively;
- list order is preserved;
- enums serialize by
name; DateTimevalues serialize as UTC ISO-8601 strings;Durationvalues serialize as integer microseconds;- primitive JSON values remain primitive values; and
- executable integrations cross the archive boundary as stable named refs.
Authors MUST NOT depend on toString() serialization of arbitrary objects. Semantically relevant custom values MUST be represented by portable data or a named extension reference.
BlueprintDefinitionDocument.fromJson rejects an unknown format, an envelope whose Blueprint identity differs from the definition, and a reference index that differs from the references extracted from the definition.
8. Compatibility
Compatibility is evaluated against semantic identity and canonical definition, not source formatting.
- Reformatting Dart without changing constructor values is compatible.
- Renaming a local Dart variable while preserving declared names is compatible.
- Changing a module, entity, facet, field, relationship, action, projection, or error identity is a semantic change.
- Reordering persisted enum values or inserting a value before existing values is rejected; supported enum evolution is append-only.
- Changing a Blueprint definition document format requires a reader for that exact format identifier.
- Readers accept v1 definition archives. New captures use v2, whose ordered
effectsarray carries an explicit effect-kind discriminator. - Physical schema or table overrides are migration concerns and do not redefine the module-qualified semantic identity.
BlueprintDefinitionDiff.between reports deterministic canonical JSON paths for added, removed, and changed values and marks destructive or security-sensitive changes where the current classifier can prove them.
9. Core conformance example
For the example above, a conforming implementation MUST observe:
final errors = BlueprintValidator.validate(equipmentBlueprint);
assert(errors.isEmpty);
final effective = equipmentBlueprint.bootstrap();
assert(effective.entity('asset.equipment') != null);
final document = BlueprintDefinitionDocument.capture(
blueprint: equipmentBlueprint,
application: const {},
);
assert(document.format == 'vyuh.blueprint.definition/v2');Capturing, encoding, decoding, and re-encoding the document MUST preserve the same canonical JSON. The captured module and entity identities MUST remain asset and asset.equipment even if physical database naming is overridden.