Diagram
100%

Drag to pan · Scroll to zoom · + / − to zoom · 0 to fit · Esc to close

Skip to content

Blueprint ​

Blueprint is the declarative model for building regulated software from connected entities. It is intentionally portable: pure Dart, JSON-safe, and free of Flutter, server, Supabase, and runtime dependencies.

The goal is to let a domain package describe what the application is before any specific surface is generated or mounted:

  • the business entities and relationships
  • the actional facets that own state and lifecycle
  • the actions users and systems can perform
  • the composable rules and tests that govern those actions
  • the evidence and audit posture required for regulated execution
  • the DB, API, UI, seed, and app intent required by compilers
  • the module lineage for every contributed item

The same model should be useful for batch production, cleaning, warehousing, in-process checks, laboratory work, transport, inventory, and other manufacturing solutions.

Philosophy ​

Blueprint is not a UI model, a database schema, or a server runtime. It is the shared language underneath all of them.

  1. Business language first. Use entities, facets, actions, policies, evidence, and audit. Avoid implementation suffixes in the domain vocabulary.
  2. Modules compose the application. A module defines entities or contributes descriptors to entities exported by another module.
  3. Entities are stable business types. manufacturing.product is the same product whether the cleaning, production, or in-process check module contributes more facets to it.
  4. Facets own actional dimensions. Tenancy, identity, governance, physical profile, cleaning profile, execution history, calibration posture, and assignment are facets.
  5. Actions are always facet-scoped. Actions that feel entity-wide belong to the IdentityFacet. Entity headers, routes, and command palettes may expose those actions, but ownership stays with the facet.
  6. Actions change state. Regulated software should not rely on direct writes when a lifecycle, policy, workflow, evidence, or audit rule applies.
  7. Every action request has context. Runtime execution captures who, when, why, what, and where through RequestContext and persists the outcome in action records.
  8. Projection is state shape. Facet projection explains how facet state is read from and written to storage/API/client state. Derived values are computed values, not projections.
  9. Server enforcement is authoritative. UI hints control affordances, but server policy checks, lifecycle checks, evidence checks, and audit writes are the enforcement boundary.
  10. Runtime surfaces share one graph. Database generation, fixed server routes, App Blueprint assembly, and Blueprint UI consume the same effective blueprint.
  11. Lineage is first-class. Every effective node can be traced to the module and descriptor that contributed it.

End-to-End Flow ​

blueprint.assemble() is the implicit assembly boundary. Callers define modules with entity descriptor contributions; assembly combines the entity graph that database generation, validators, explorers, app assembly, and runtime adapters consume.

Object Model At A Glance ​

ModelMeaningPrimary owner
BlueprintComplete application definition.Application package
ModulePackage-level boundary that defines or contributes domain content.Domain package
ModuleExportExplicit export of entities, derived values, or actions.Module
EntityDescriptorContribution to an entity type, identified by target and aspect.Same module or dependency module
EntityAssembled business object and facet composition boundary.Entity assembler
FacetActive dimension of an entity.Entity or descriptor
FieldTyped state member.Facet
RelationshipEntity-to-entity connection.Facet
LifecycleFacet-owned state machine.Facet
TransitionAllowed lifecycle move.Lifecycle
ActionFacet-scoped transactional action surface.Facet
RequestContextActor, scope, time, reason, source, and correlation envelope.Runtime protocol
ActionRecordDurable runtime truth for a request.Runtime protocol
ActionResultFactual output produced by an action record.Runtime protocol
ActionFailureTyped failure produced during planning, preflight, or execution.Runtime protocol
RuleComposable rule for access, policy, lifecycle, data, evidence, audit, snapshot, effect, or custom decisions.Action/entity/module
Rule.test (Expr<bool>)Typed expression a rule evaluates: field comparisons, lifecycle state, Actors and Facts. Language in vyuh_expression.Rule
Actors / Facts / FieldOfBuilders for actor expressions, host facts and typed field reads.Rule test, assignment
EvidenceRequired or captured proof.Facet/action/runtime
ActionCaptureAuthored proofs, signature, reason, and retention for an action.Action
DerivedValueComputed value from DB or server.Facet/entity/module
FacetProjectionFacet-owned projection.Facet
EntityProjectionUnion of all facet projections.Effective entity
EntityUIOverrideDescriptor-level UI posture for icon, category, route/list visibility, route prefix, and table columns.Entity descriptor
EffectDownstream action emitted by transitions or actions.Facet/action
TriggerRuntime trigger descriptor.Module
SubscriptionCross-facet or cross-entity reaction declaration.Facet/module
ModuleDb / EntityDb / FieldDbDB and performance hints.Module/entity/field
ModuleUI / EntityUI / FacetUI / FieldUI / ActionUIFlutter-free UI hints.Module/entity/facet/field/action
DartRefStatic code reference without runtime dependency.Any model that delegates logic
EntityOriginGraphLineage for every effective node.Entity assembler

Application and Module Composition ​

A Blueprint is made from modules. A module can define entities, declare policies, expose DB/UI/seed hints, export reusable items, and contribute descriptors to entities defined elsewhere.

dart
final blueprint = Blueprint(
  name: 'manufacturing_super_app',
  version: '1.0.0',
  modules: [
    manufacturingModule,
    iamModule,
    cleaningModule,
    productionModule,
  ],
);

final effective = blueprint.assemble();
final product = effective.entity('manufacturing.product');

The base manufacturing module defines shared entities such as product, equipment, area, material, method, and equipment train. The cleaning and production modules should not create competing product types. They contribute descriptors to manufacturing.product.

Entity ​

An entity is a stable business object and composition boundary. It carries a schema type, title, description, facets, aggregate projections, DB hints, UI hints, and seed hints. It does not own actions directly.

Examples:

  • manufacturing.product
  • manufacturing.equipment
  • manufacturing.area
  • iam.user
  • iam.user_group
  • iam.role
  • cleaning.protocol
  • production.batch

The entity is the unit users think about, APIs route around, tables list, and permissions often target. The descriptor is the unit modules contribute. The facet is the unit that owns state dimensions and behavior. Every assembled entity must have exactly one IdentityFacet. If an action feels entity-wide, model it on the IdentityFacet.

Entity Descriptor ​

An EntityDescriptor contributes facets to an entity address. It is the mechanism that lets modules layer into the same stable type. target says which entity receives the contribution; aspect says which named module/domain aspect made the contribution.

text
manufacturing.product
  identity
  tenancy
  governance
  composition

cleaning descriptor -> manufacturing.product
  cleaning profile
  residue limit participation
  protocol impact

production descriptor -> manufacturing.product
  execution history
  batch record applicability

After assembly, the effective entity is one product:

text
manufacturing.product
  identity
  tenancy
  governance
  composition
  cleaning profile
  residue limit participation
  protocol impact
  execution history
  batch record applicability

Identity is also facet-owned. The assembler/validator requires exactly one IdentityFacet across the base module and all descriptor contributions. A generic Facet(name: 'identity') is invalid because identity is a typed facet, not just a reserved string.

Descriptors may contribute new facets or merge into an existing facet. Shared facets such as governance and tenancy are expected to accept contributions from many modules.

Descriptors may also describe entity-level UI posture through EntityUIOverride. This is how a module gives an entity an icon, category, menu/list visibility, route prefix, table columns, and default sort without creating an app descriptor. UI descriptors are assembled into Entity.ui; they are not storage facets and do not imply lifecycle, audit, or action ownership. Unique UI posture must be singular and conflict-free. Table columns are the one additive part, because multiple descriptors can safely add visible columns.

Facet ​

A facet is an actional dimension of an entity. It is the main extensibility unit in the blueprint.

A facet can contain:

  • title and description
  • fields
  • relationships
  • lifecycle
  • actions
  • rules and tests
  • evidence declarations
  • audit envelope
  • derived values
  • subscriptions and effects
  • facet projection
  • UI hints

This lets the blueprint say that an equipment entity has several independent regulated dimensions:

text
equipment
  identity
  tenancy
  governance
  physical profile
  cleaning status
  calibration posture
  maintenance posture
  execution usage

Not every facet needs a lifecycle. Some facets are mostly state and relationships. Others are actional and define transitions, actions, evidence, rules, and tests.

Facet or mapping entity? ​

Use a facet when the state is one dimension of the same entity instance. A user profile, tenancy defaults, authentication metadata, governance status, or geo snapshot can be facets because each facet row is at most one-to-one with the user.

Do not model a repeatable relationship as a facet just because it is "about" the entity. If the relationship can have multiple rows per entity, its own validity window, approval state, revocation, scoped grants, evidence, audit, or lifecycle, make it a separate mapping entity. Examples include iam.membership, iam.role_assignment, and a possible iam.user_site_membership.

The decision rule is:

QuestionPrefer a facet when...Prefer a separate entity when...
CardinalityThere is at most one row of this state per owning entity.There can be many rows per owning entity, or the same other entity can be linked many times under different scopes.
IdentityThe state has no useful identity apart from the owner.The link or child needs its own id, natural key, record page, API route, or references from other records.
LifecycleThe state changes with the owner or one facet lifecycle.The link can be drafted, activated, suspended, revoked, expired, reapproved, or corrected independently.
Audit and evidenceChanges can be audited as changes to the owner facet.The link itself needs approval evidence, reason codes, signatures, or a review trail.
PermissionsAccess follows the owner.Access depends on the link's scope, role, site, department, or assignment state.
Query shapeUsers mostly read it as part of the owner detail.Users list, filter, approve, revoke, or report on the relationship records themselves.
text
iam.user
  identity
  tenancy          # primary/default org context, home site hints
  profile
  authentication
  governance

iam.user_site_membership
  identity         # user_id + site_id + membership_type
  scope            # department, valid_from, valid_until
  governance       # draft -> active -> suspended -> revoked

The iam.user.tenancy facet can say "this user has these defaults or home sites." The iam.user_site_membership entity says "this specific user-site assignment exists, is active, was approved, expires later, and can be revoked without changing the rest of the user." That difference is the boundary.

Cardinality is necessary but not the only reason. A single optional relationship can still become a separate entity if it has independent lifecycle or regulated evidence. Conversely, a small list such as home_site_ids can stay on a facet when it is only a hint or default, not the system-of-record for access.

For a single assignment table that maps users to products at sites, model the assignment itself as the entity. Declare the physical FK fields first, then bind each relationship to the field that stores it:

dart
const userId = ReferenceIdField('user_id');
const siteId = ReferenceIdField('site_id');
const productId = ReferenceIdField('product_id');

final userSiteProductAssignment = Entity(
  name: 'user_site_product_assignment',
  schema: 'iam',
  uniqueKeys: const [
    [TenantIdField(), userId, siteId, productId],
  ],
  facets: const [
    IdentityFacet(
      fields: [
        userId,
        siteId,
        productId,
        TextField('assignment_type'),
      ],
      relationships: [
        Relationship(
          name: 'user',
          target: EntityRef(module: ModuleRef('iam'), name: 'user'),
          cardinality: RelationshipCardinality.hasOne,
          field: userId,
        ),
        Relationship(
          name: 'site',
          target: EntityRef(module: ModuleRef('iam'), name: 'site'),
          cardinality: RelationshipCardinality.hasOne,
          field: siteId,
        ),
        Relationship(
          name: 'product',
          target: EntityRef(module: ModuleRef('iam'), name: 'product'),
          cardinality: RelationshipCardinality.hasOne,
          field: productId,
        ),
      ],
    ),
  ],
);

This emits one identity table with user_id, site_id, product_id, a unique tuple over those fields, and foreign keys from those fields to the target tables. The relationship does not invent a second column when field is provided.

Fields and Field Types ​

Fields are typed members owned by facets. Titles and descriptions are core metadata, not UI-only data, because DB, API, UI, docs, localization, seed, and analytics all need human-readable semantics.

The type model is portable and maps cleanly to Postgres, API schemas, and UI widgets:

  • text, integer, bigint, double, decimal
  • boolean
  • date, timestamp, interval
  • UUID
  • JSONB
  • enum
  • decision

Fields may carry DB hints, UI hints, and seed hints. For example, a field can declare that it is indexed, commonly filtered, visible in a table, editable only under a policy, or seeded from a regulated synthetic distribution.

Relationships ​

Relationships declare how entities connect:

  • hasOne
  • hasMany
  • manyToMany

They also declare cascade rules, embed hints, join semantics, and lineage. This is where manufacturing graph structure starts: products use equipment trains, equipment belongs to areas, sampling locations belong to equipment, protocols reference methods, and assignments bind actors to scoped resources.

For simple ownership or containment, a relationship usually lowers to a foreign key. The preferred explicit form is: declare a ReferenceIdField on the same facet, then bind the relationship with field: thatReferenceField. The relationship supplies graph meaning, target entity, cascade behavior, embed hint, and requiredness; the field supplies the physical column name.

If field is omitted, generators preserve the older convention and derive a foreign-key column named <relationship_name>_id. Use that only for simple cases where the conventional column name is exactly what you want.

The validator enforces that an explicit relationship field is declared on the same facet and is a ReferenceIdField. It is not valid to bind a relationship to a text, enum, JSON, or other scalar field.

For rich many-to-many membership, prefer an explicit mapping entity whose identity facet points at both sides. This keeps lifecycle, approval, validity, scope, and audit on the assignment record instead of hiding them in a generic join table or overloading one side's facet.

Lifecycle and Transitions ​

Lifecycle belongs inside a facet. A single entity can have many state dimensions, but each dimension should have one clear owner.

Examples:

text
equipment.cleaning_status
  dirty -> cleaned -> sampled -> released

protocol.approval
  draft -> reviewed -> approved -> effective -> superseded

material.quality_status
  quarantine -> released -> blocked

Transitions reference rules and declare effects. When a workflow is required, the transition is still the business state change; the workflow is the orchestration used to reach it.

Actions, Requests, and Records ​

An action is the transactional surface of a facet. It may create, update, approve, release, calculate, assign, sample, review, reject, rework, or retire state. The owning facet represents the intent:

  • identity owns create, publish, archive, restore, purge, and other actions that establish or retire the entity record.
  • assignment owns assign, reassign, delegate, claim, and release assignment.
  • lifecycle or execution owns start, pause, resume, submit, complete, and cancel operational work.
  • evidence owns capture, replace, verify, and reject proof.
  • policy_snapshot owns resolve, refresh, freeze, and explain effective policy.
  • exception owns raise, triage, remedy, clear, and reopen exceptions.

Actions declare:

  • payload fields
  • rules
  • evidence requirements
  • audit envelope
  • effects and triggers
  • UI hints

Rules are the complete declarative decision surface for an action. Use RuleKind to name the concern and RulePhase to name when it runs:

  • RuleKind.access with Facts.grant or an Actors expression for grants and permissions.
  • RuleKind.policy with Facts.policyAllows for effective policy checks.
  • RuleKind.lifecycle with StateIn or an evaluator for state moves.
  • RuleKind.data with field comparisons (Comparison over FieldValue<T>(FieldOf<T>(field))) or an evaluator for payload and entity invariants.
  • RuleKind.evidence, audit, snapshot, and effect for regulated proof, capture, and downstream work.
  • RulePhase.availability, beforeCommit, afterCommit, and async to place a rule in the transaction timeline.

A rule's test is an Expr<bool> from vyuh_expression, the vocabulary of logic. And, Or, and Not compose smaller checks with Kleene logic: the order of operands never changes the answer, and an unknown fact fails closed, also under Not. Built-in nodes cover common entity work, while Facts.evaluator(HostEvaluatorRef(...)) gives runtimes a stable hook for logic that must be implemented in Postgres, Vyuh Policy, or application code. Facts (a grant, a qualification, a policy decision, evidence, an evaluator outcome) are resolved by the host before the expression runs; the evaluation trace is stored with the rule record as expression_trace.

At runtime, an ActionRequest carries RequestContext:

  • who: actor, roles, groups, delegation, signature principal
  • when: timestamp and clock source
  • why: reason, justification, change request, deviation, or ticket
  • what: action, payload, target entity, previous state, intended state
  • where: tenant, site, area, workstation, device, session, IP, correlation ID

The blueprint declares what must be captured and which rules must pass. The runtime captures, validates, plans, executes, and persists the outcome as an ActionRecord. Records can contain ActionResult values, ActionFailure values, rule records, command records, effect records, and integrity records.

Policies, Rules, and Access ​

Policy is a first-class configuration language owned by vyuh_policy. It is not hidden under rules.

A Policy defines:

  • domains
  • sections
  • typed parameters
  • scope ladders
  • PolicyScope addresses
  • scoped policy values
  • domain, section, and parameter locks
  • source lineage

Policy scope is broad-to-specific. The standard cascade is global, tenant, site, module, entity, facet, action, and instance, with additional domain scopes allowed by a policy's scope ladder. Nearer scope normally overwrites farther scope. A PolicyLock can protect a domain, section, or parameter so a nearer policy cannot override it.

PolicyScope scopes the entire policy contribution: values, locks, and source lineage. The effective-policy trace records each contribution as scope plus source plus values plus locks plus result.

The policy resolver does not decide whether an action is allowed. It only produces EffectivePolicy. Action/rule evaluation consumes that effective policy with the ActionRequest, actor, payload, entity state, and evidence. If custom runtime logic is needed, it belongs to a host evaluator, not to the policy object itself.

At runtime, Vyuh Policy resolves an EffectivePolicy for the current request scope and records a trace of each scoped contribution. That effective policy becomes part of the execution plan and decision trace.

Blueprints reference policies through Rule values with RuleKind.policy and Facts.policyAllows. The rule does not define the policy; it asks the runtime to evaluate the already-resolved effective policy. Access uses a related rule mechanism: RuleKind.access and Facts.grant.

Policy rules are not projections. They are named decisions computed from effective policy, request context, actor, state, and declared policy reads.

Evidence and Audit ​

Evidence and audit are related but not identical.

Evidence is proof. It can be a checklist, signature, reason, artifact, photo, instrument file, log excerpt, training acknowledgement, external reference, or other record.

Audit is the immutable history and compliance envelope. It records the action, actor, request context, previous state, new state, evidence links, policy decisions, exceptions, signatures, failures, results, and runtime trace.

In GMP/ALCOA+ terms, the runtime must make actions:

  • attributable
  • legible
  • contemporaneous
  • original
  • accurate
  • complete
  • consistent
  • enduring
  • available

Blueprint declares evidence requirements and audit posture. Runtime packages capture and enforce the actual records.

Derived Values ​

DerivedValue represents computed values. It supports several tiers:

  • generated column
  • SQL view
  • materialized view
  • server evaluator
  • server evaluator

Derived values are useful for residue limit calculations, equipment surface summaries, readiness scores, exception counts, overdue-review flags, search labels, effective status, and impact summaries.

Use a derived value when the value is computed from other facts. Use a facet projection when describing how facet state is exposed, flattened, indexed, read, and written.

Facet Projection and Entity Projection ​

Facets own state, so facets also own projection.

FacetProjection describes how the state inside one facet is exposed:

  • which field path is projected
  • whether it is read-only or read-write
  • whether it is stored inline, in a facet table, in JSONB, in a view, or in a derived source
  • how it should behave for filtering, sorting, search, grouping, and aggregation
  • whether it is visible or editable through generated UI surfaces

EntityProjection is the union of all facet projections for an effective entity. It provides the bridge between normalized facet storage and simple client-side state.

text
Facet state
  identity.code
  identity.title
  tenancy.tenant_id
  tenancy.site_id
  physical_profile.surface_area_cm2
  cleaning_status.current_state

Entity projection
  code
  title
  tenant_id
  site_id
  surface_area_cm2
  current_cleaning_state

The runtime and compilers can preserve facet boundaries internally while giving client code a simple property surface:

dart
entity.property('surface_area_cm2');
entity.property('current_cleaning_state');

The projection model also gives the DB compiler enough intent to create typed columns, indexes, JSONB columns, views, and read models without forcing the UI or API to understand every storage split.

UI Mapping ​

Blueprint UI hints are Flutter-free. They describe what should be generated, not how Flutter renders it.

UI hints can express:

  • menu and route visibility
  • list columns
  • form sections
  • field widgets
  • field visibility and editability
  • action placement
  • action availability
  • evidence prompts
  • signature and reason prompts
  • redaction, hide, disable, placeholder, and read-only fallbacks
  • icons and categories through portable references

BlueprintAssembler derives the standard product surfaces from these hints. vyuh_blueprint_ui maps them into its Studio widgets, while typed client bindings provide specialized Flutter renderers. The server remains authoritative for enforcement.

See the complete UI hint vocabulary, including the entity → module → Blueprint override cascade.

Seed Mapping ​

Seed hints describe the data a module needs for smoke tests, demos, validation, and load scenarios. They are separate from DB hints because seeds can be served through SQL, API fixtures, generated JSON, AI-generated packs, or scenario scripts.

Seed hints can express:

  • dataset size
  • realism level
  • required entities and relationships
  • field value strategy
  • distributions and examples
  • uniqueness and reference behavior
  • validation scenario intent

Manufacturing Layering Example ​

text
manufacturing module
  product
    identity
    tenancy
    governance
    composition
  equipment
    identity
    tenancy
    governance
    physical profile
  area
    identity
    tenancy
    hierarchy

cleaning module
  descriptor -> manufacturing.product
    cleaning profile
    residue limit participation
  descriptor -> manufacturing.equipment
    cleaning status
    sampling locations
  entities
    protocol
    assessment
    result

production module
  descriptor -> manufacturing.product
    execution history
  descriptor -> manufacturing.equipment
    usage history
  entities
    batch
    batch step

The result is not three disconnected applications. It is one effective graph where shared manufacturing entities accumulate regulated dimensions from each solution package.

Direct Dart Authoring ​

Blueprint is authored directly as a Dart object graph:

dart
final cleaningProfile = Facet(
  name: 'cleaning_profile',
  title: 'Cleaning Profile',
  fields: [
    Field(
      name: 'worst_case_rank',
      title: 'Worst Case Rank',
      type: IntegerType(),
    ),
  ],
);

Typed Dart declarations remain the executable, compile-safe product layer. Blueprint also has a portable, editable BlueprintDefinitionDocument used by Studio to model and compose ontologies independently of a running app. The definition does not manufacture arbitrary Dart objects: runtime type catalogs and explicit bindings attach typed host evaluators, effects, workflows, storage, and custom UI implementations before execution. No annotations, generated authoring classes, build_runner, or YAML authoring layer is required.