Skip to main content

Blueprints

Blueprints are versioned, declarative bundles of Construction Kit (CK) models and runtime seed data that bootstrap a tenant — and continue to manage it across its lifetime. A blueprint can be installed, re-applied, updated, rolled back, uninstalled, and may depend on other blueprints. Versioned migration scripts transform tenant data when a blueprint's own version moves forward.

What a Blueprint Is

A blueprint is essentially three things rolled into one artifact:

  • CK model dependencies — the schema (types, attributes, associations) the tenant needs before the seed data can be imported. The engine resolves and imports those CK models on apply.
  • Seed data — a runtime-model YAML file with the entities that should exist in the tenant after the apply.
  • Migration scripts — optional, versioned transformations executed when the blueprint moves from an older version to a newer one in an already-installed tenant.

Where a CK model defines the shape of data, a blueprint puts the first real data into a tenant. The two work in tandem: CK models alone leave a tenant empty; blueprints turn an empty tenant into a working one.

Properties

PropertyDescription
VersionedSemVer (MyBlueprint-1.2.3). Version ranges express compatibility, like CK models.
Dependency-awareA blueprint may depend on other blueprints, resolved transitively at install time.
Owner-trackedEvery seed entity is tagged with rtBlueprintSource and rtBlueprintLocked.
UpdatableTenants are moved to newer versions via Safe / Merge / Full / Migration modes.
Rollback-ableDestructive operations create a tenant backup; Rollback restores the snapshot.
Multi-installA tenant can host several blueprints concurrently. Refcounted, cascade-uninstall optional.

Tenant-Scoped Storage

Blueprint registry rows (BlueprintInstallation, BlueprintHistory, BlueprintBackup) live as CK entities inside the tenant's own runtime repository, alongside the seed data they describe. There is no cross-tenant collection — an apply in tenant X never writes outside tenant X. mongodump --db=<tenant> captures the registry along with the entities.

Blueprint Structure

A blueprint is a directory containing a blueprint.yaml, an optional seed-data file, and optional migration scripts:

MyBlueprint-1.0.0/
├── blueprint.yaml
├── seed-data/
│ └── entities.yaml
└── migrations/
└── from-1.0.0.yaml

Blueprint YAML Schema

$schema: https://schemas.meshmakers.cloud/blueprint-meta.schema.json
blueprintId: InfrastructureStarter-1.0.0
description: Infrastructure management starter blueprint

# CK models loaded into the tenant when this blueprint is applied
ckModelDependencies:
- System-[2.0,3.0)
- Commerce-[1.0,2.0)

# Other blueprints required before this one (resolved transitively, topo-sorted)
blueprintDependencies:
- BaseEntities-[1.0,)
- SecurityModel-[2.0,)

# Optional path to seed data (relative to blueprint root)
seedDataPath: seed-data/entities.yaml

# Optional migrations from older versions of this blueprint
migrations:
- fromVersion: "0.9.0"
scriptPath: "migrations/from-0.9.0.yaml"
FieldTypeDescription
$schemastringSchema URI for validation
blueprintIdstringUnique ID with version (Name-Major.Minor.Patch)
descriptionstringOptional description
ckModelDependenciesstring[]CK models with version ranges (auto-imported on apply)
blueprintDependenciesstring[]Other blueprints with version ranges (resolved transitively)
seedDataPathstringOptional path to seed-data file (runtime-model format)
migrationsarrayOptional list of migration scripts keyed by source version

Version Ranges

Both ckModelDependencies and blueprintDependencies use the same range syntax as CK models:

FormatMeaning
1.0.0Exact version
[1.0.0,)Version 1.0.0 or higher
[1.0.0,2.0.0)Version >= 1.0.0 and < 2.0.0
(1.0.0,2.0.0]Version > 1.0.0 and <= 2.0.0
[1.5.0]Exactly version 1.5.0

Seed Data Format

Seed data is a runtime-model YAML file. The blueprint engine stamps the source attributes during import; you do not write them yourself.

$schema: https://schemas.meshmakers.cloud/runtime-model.schema.json
dependencies:
- System-2.0.0
entities:
- rtId: 507f1f77bcf86cd799439011
ckTypeId: System/Entity
rtWellKnownName: InitialEntity
attributes:
- id: System/Name
value: My Initial Entity
- id: System/Description
value: Created by blueprint

Seed data is applied with upsert strategy: existing entities (matched by rtId) are updated; new ones are inserted.

Lifecycle

Apply Flow

ApplyBlueprintAsync(tenantId, blueprintId, force)

├── 1. Resolve transitive blueprint dependency closure (topo-sorted)

├── 2. Conflict-check (CK versions, entity ownership, rtId collisions)
│ → BlueprintApplicationResult.Conflicts; abort on hard conflicts

├── 3. For each blueprint in topo order:
│ ├── Idempotency: same version installed → no-op
│ │ same version installed + --force → re-apply (upsert)
│ │ different version installed → Update path
│ ├── Import CK model dependencies (auto-resolve via ICkModelUpgradeService)
│ ├── Apply seed data via IImportRtModelCommand (Upsert)
│ ├── Tag entities with rtBlueprintSource / rtBlueprintLocked / rtBlueprintAppliedAt
│ ├── Persist BlueprintInstallation
│ └── Publish BlueprintApplied event

└── 4. Append history entry, return result

Entity Source Tracking

Every seed entity is stamped with three system attributes when applied:

AttributeTypeDescription
rtBlueprintSourcestringOwning blueprint, full id (Infrastructure-1.0.0). Exactly one owner per entity.
rtBlueprintLockedbooltrue = managed by blueprint, updates will overwrite; false = user-released.
rtBlueprintAppliedAtDateTimeUTC timestamp of the most recent apply/update touching this entity.

A blueprint that ships an entity but wants to leave it user-editable from day one can set rtBlueprintLocked: false in its seed data.

Creating a Blueprint

A blueprint is just three files (or two if it has no migrations). The minimum viable case:

Step 1 — Create the folder layout

HelloBlueprint-1.0.0/
├── blueprint.yaml
└── seed-data/
└── entities.yaml

Step 2 — Write blueprint.yaml

$schema: https://schemas.meshmakers.cloud/blueprint-meta.schema.json
blueprintId: HelloBlueprint-1.0.0
description: |
Seeds two AutoIncrement counters used by the demo invoice and order flow.
ckModelDependencies:
- System-[2.0,3.0)
seedDataPath: seed-data/entities.yaml

Step 3 — Write seed-data/entities.yaml

$schema: https://schemas.meshmakers.cloud/runtime-model.schema.json
dependencies:
- System-2.0.0
entities:
- rtId: 65d5c447b420da3fb1238201
ckTypeId: System/AutoIncrement
rtWellKnownName: InvoiceCounter
attributes:
- id: System/AutoIncrement.End
value: 999999
- id: System/AutoIncrement.CurrentValue
value: 1000
- id: System/AutoIncrement.Format
value: "INV-{0:D6}"
- rtId: 65d5c447b420da3fb1238202
ckTypeId: System/AutoIncrement
rtWellKnownName: OrderCounter
attributes:
- id: System/AutoIncrement.End
value: 99999
- id: System/AutoIncrement.CurrentValue
value: 100
- id: System/AutoIncrement.Format
value: "ORD-{0:D5}"

Step 4 — Place the folder into a catalog

The simplest catalog is the local file system. The asset-repo service expects blueprints at the path configured via LocalFileSystemBlueprintCatalogOptions.RootPath. For local development that defaults to ~/.octo/local-blueprint-catalog/:

~/.octo/local-blueprint-catalog/
└── blueprints/v1/
└── HelloBlueprint/
└── 1.0.0/
├── blueprint.yaml
└── seed-data/
└── entities.yaml

For published catalogs (GitHub-Pages-hosted), the same layout applies, but a generated catalog.json index sits at each level — see GitHub Catalog Layout below.

Step 5 — Install

octo-cli -c InstallBlueprint -b HelloBlueprint-1.0.0

Or via Refinery Studio: see the Studio user guide.

Building On Top of Another Blueprint

Blueprints compose through dependencies, not by bundling. To extend an existing blueprint:

  1. Declare the dependency in your blueprint.yaml:

    blueprintDependencies:
    - HelloBlueprint-[1.0,2.0)
  2. Reference its CK models indirectly. You don't list them again — the dependency closure already imports them.

  3. Add your own seed data. Your entities live in your blueprint's rtBlueprintSource namespace; the entities from HelloBlueprint remain owned by it.

  4. Apply normally. The engine resolves the transitive closure, applies dependencies first (topo-sorted), then your blueprint:

    octo-cli -c InstallBlueprint -b ExtendedHello-1.0.0

    ListBlueprintInstallations then shows both blueprints — HelloBlueprint carries IsDependency: true.

Ownership Rules

  • Each entity has exactly one owning blueprint (rtBlueprintSource).
  • A dependent blueprint cannot mutate entities owned by its dependencies through seed data — that path raises a UserModified / OwnershipConflict during apply.
  • If the dependent really needs to evolve those entities, ship a migration script that targets them by rtId or by rtWellKnownName + blueprintSourceOnly: true.

Uninstall and Refcounts

  • Uninstalling ExtendedHello does not remove HelloBlueprint if any other installed blueprint still depends on it (refcount > 0).
  • octo-cli -c UninstallBlueprint -n ExtendedHello -c cascades: removes ExtendedHello and orphan-cleans transitively-depending blueprints whose refcount drops to zero.

Update Modes

ModeBehaviour
SafeAdd new entities only. Existing entities are left alone, even if locked.
MergeAdd new + upsert locked entities. Unlocked entities raise UserModified conflicts (default: skip).
FullLike Merge, plus delete entities that exist in the tenant but no longer in the seed. Unlocked → conflict.
MigrationExecute the migration script from the installed version to the target. Required for any non-additive change.

A pre-update backup is created by default (CreateBackup = true). Disable it with CreateBackup = false only when you have an alternate snapshot mechanism.

Conflict Resolution

A conflict is raised when an unlocked entity (rtBlueprintLocked = false) is in the way of an update.

TypeTriggered when
UserModifiedThe seed wants to update this entity, but the tenant entity has been unlocked.
DeleteModifiedFull mode wants to delete this entity (no longer in seed), but the tenant entity has been unlocked.

Default per-entity resolution is Skip. The caller can override per-entity:

ResolutionBehaviour
KeepUserKeep the user's version, skip the blueprint change.
KeepBlueprintApply the blueprint's version. UserModified: seed is re-applied and the entity is re-locked. DeleteModified (Full only): entity is erased.
MergeCurrently treated as KeepUser (semantic 3-way merge is out of scope).
SkipSkip this entity.

Migration Scripts

For non-additive changes (rename, delete, transform), ship a migration script and reference it from blueprint.yaml:

# MyBlueprint-2.0.0/migrations/from-1.0.0.yaml
$schema: https://schemas.meshmakers.cloud/blueprint-migration.schema.json
sourceVersion: "1.0.0"
targetVersion: "2.0.0"
description: "Migration from v1 to v2"

preConditions:
- type: EntityExists
target:
ckTypeId: System/Entity
rtWellKnownName: MainConfig

steps:
- stepId: rename-config-field
action: Transform
target:
ckTypeId: System/Entity
blueprintSourceOnly: true
transform:
type: Rename
sourceAttribute: LegacyVersion
targetAttribute: Version

- stepId: delete-deprecated
action: Delete
target:
ckTypeId: System/Entity
rtWellKnownName: LegacyConfig
blueprintSourceOnly: true

postValidations:
- validationId: still-have-config
type: EntityCount
target:
ckTypeId: System/Entity
expectedCount: 5
severity: Error

Reference from the manifest:

migrations:
- fromVersion: "1.0.0"
scriptPath: "migrations/from-1.0.0.yaml"

Supported step actions

ActionPurpose
AddInsert an entity (data carries the full RtEntityTcDto payload).
UpdateUpdate attributes on matching entities (data is a { attributeName: value } dict).
DeleteErase matching entities (DeleteOptions.Erase — permanent).
RenameRename an attribute on matching entities (shorthand for Transform of type Rename).
TransformType-driven: Rename, Copy, Delete, SetValue, MapValue.

For CK model migrations (when the schema itself evolves), see CK Model Migrations. Blueprint migrations transform runtime entities; CK migrations transform the schema and the entities together.

Multi-Blueprint Installation

A tenant can host any number of blueprints concurrently. Two services track this state:

InterfacePurpose
ITenantBlueprintInstallationsThe current set of installed blueprints (one row per blueprint, with IsDependency flag).
ITenantBlueprintHistoryAppend-only operation log (install, update, rollback, uninstall) with timestamps and counts.

Both are persisted as CK entities (System/BlueprintInstallation, System/BlueprintHistory) inside the tenant's own runtime repository.

Backup and Rollback

Every update creates a pre-update backup by default. Rollback restores the entire tenant snapshot — it is a full restore, not a semantic undo of migration steps. After rollback, the BlueprintInstallation rows match the snapshot; partial undo is not supported.

Catalogs

CatalogDescription
LocalFileSystemBlueprintCatalogLoads blueprints from the file system.
EmbeddedResourceBlueprintCatalogLoads blueprints from assembly resources.
PublicGitHubBlueprintCatalogReads blueprints from a public GitHub Pages site.
PrivateGitHubBlueprintCatalogReads blueprints from a private/internal GitHub repository (writes via Octokit).

GitHub Catalog Layout

blueprints/v1/
├── catalog.json # Root catalog index
└── m/ # First letter of blueprint name (lowercase)
└── MyBlueprint/
├── catalog.json # Library catalog (one entry per major version)
└── 1/
├── catalog.json # Version catalog (list of versions)
└── MyBlueprint-1.0.0/
├── blueprint.yaml
├── seed-data/
└── migrations/

The three catalog.json levels are generated by the publish flow on the engine side — application code talks to IBlueprintCatalogManager, not to the catalog files directly.

Best Practices

  1. Small, focused blueprints. One blueprint per domain/feature. Compose via blueprintDependencies, not by bundling unrelated entities.
  2. Use version ranges for dependencies. [1.0,) keeps things flexible; pinning exact versions is fine for blueprintId but rarely useful in dependencies.
  3. Sparse seed data. Ship essential bootstrap data only — no test data, no per-customer specifics.
  4. Lock managed entities. Default rtBlueprintLocked is true; only override to false when you intend the user to take ownership immediately.
  5. Migration scripts for breaking changes. Schema renames, deletes, and value transformations need an explicit script. Additive changes work via Merge alone.
  6. Test with dry-run. UpdateBlueprint -dr simulates without persisting.
  7. Keep backups. Default backup creation is on; only disable it when you have an alternate snapshot mechanism.

See Also