Skip to content

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.

text
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 UIBehavior

Identity 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:

text
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

dart
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:

text
module                     asset
entity                     equipment
qualified entity           asset.equipment
facet                      identity
qualified action           asset.equipment.identity.update

ModuleRef, 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:

  1. the consuming module declares the source module in dependsOn; and
  2. 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:

text
code     blueprint.validation.<identifier>
path     dotted location inside the Blueprint
message  human-readable reason

Tools MUST use code for programmatic handling, SHOULD use path to focus the offending declaration, and MAY render message directly.

Representative diagnostics include:

CodeMeaning
blueprint.validation.identity_facet_missingAn entity has no typed identity facet.
blueprint.validation.duplicate_fieldTwo effective fields have the same identity in one facet.
blueprint.validation.module_cycleModule dependencies are cyclic.
blueprint.validation.cross_module_entity_not_exportedA consumer reaches an entity the owner did not export.
blueprint.validation.lifecycle_ambiguous_transitionMore than one transition matches the same state and trigger.
blueprint.validation.effect_payload_type_mismatchAn effect maps incompatible source and target fields.
blueprint.validation.workflow_effect_invalid_refA workflow start does not use a valid typed workflow ref.
blueprint.validation.enum_evolution_not_append_onlyA 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:

  1. resolves module and entity identities;
  2. applies descriptor contributions in deterministic order;
  3. merges facet, action, projection, query, and UI declarations;
  4. records origin information; and
  5. 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:

text
vyuh.blueprint.definition/v2

The 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;
  • DateTime values serialize as UTC ISO-8601 strings;
  • Duration values 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 effects array 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:

dart
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.

Blue is the Vyuh Blueprint documentation surface.