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
| Property | Description |
|---|---|
| Versioned | SemVer (MyBlueprint-1.2.3). Version ranges express compatibility, like CK models. |
| Dependency-aware | A blueprint may depend on other blueprints, resolved transitively at install time. |
| Owner-tracked | Every seed entity is tagged with rtBlueprintSource and rtBlueprintLocked. |
| Updatable | Tenants are moved to newer versions via Safe / Merge / Full / Migration modes. |
| Rollback-able | Destructive operations create a tenant backup; Rollback restores the snapshot. |
| Multi-install | A 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"
| Field | Type | Description |
|---|---|---|
$schema | string | Schema URI for validation |
blueprintId | string | Unique ID with version (Name-Major.Minor.Patch) |
description | string | Optional description |
ckModelDependencies | string[] | CK models with version ranges (auto-imported on apply) |
blueprintDependencies | string[] | Other blueprints with version ranges (resolved transitively) |
seedDataPath | string | Optional path to seed-data file (runtime-model format) |
migrations | array | Optional list of migration scripts keyed by source version |
Version Ranges
Both ckModelDependencies and blueprintDependencies use the same range syntax as CK models:
| Format | Meaning |
|---|---|
1.0.0 | Exact 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 |
For ckModelDependencies, the blueprint engine installs the range's lower bound on a fresh tenant (a tenant that already holds the same model at or above the floor is left untouched — no downgrade). The floor version must therefore be installable together with all sibling dependencies; after a dependency bump, raise the floor to the first version compiled against it. The seed-data file declares its own dependencies: pins independently of the manifest — keep both in sync. See Versioning Rules for the full picture.
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
Force Re-Apply and Runtime State
Re-applying an installed blueprint version with --force (octo-cli -c InstallBlueprint -b MyBlueprint-1.0.0 -f) upserts every seed entity back to its seed values. This is the intended way to repair drifted seed data — but it also means any attribute value that operators or services changed after install is reset to what the seed ships, unless one of the following protects it:
- Unlocked entities (
rtBlueprintLocked = false) go through conflict resolution instead of being overwritten silently. - Runtime-state attributes are preserved. A CK attribute flagged
isRuntimeState: truein its model declares that its value is owned by the running system, not by seed data: the runtime import keeps the tenant's current value on upsert and applies the seed value only when the entity is first inserted. The preserve logic sits in the shared runtime-model import, so it covers blueprint applies and plainImportRt -ralike.
Well-known runtime-state attributes:
| Model | Attributes | Since |
|---|---|---|
System.StreamData | Archive.Status — a force re-apply no longer deactivates activated archives | 1.7.0 |
System.Communication | Hostname, IngressEnabled, ChartVersion, ValuesYaml, Values on workloads — operator- or CD-set deployment values survive a force re-apply | 3.28.0 |
When authoring a blueprint, avoid shipping mutable operational state (status flags, counters, deployment values) in seed entities. If the attribute is not flagged isRuntimeState in its CK model, every force re-apply resets it to the seed value — which can silently disable archives, undo deployment configuration, or reset processing watermarks on a production tenant. Either leave such attributes out of the seed, or flag them isRuntimeState in the owning CK model (a MINOR model change).
Entity Source Tracking
Every seed entity is stamped with three system attributes when applied:
| Attribute | Type | Description |
|---|---|---|
rtBlueprintSource | string | Owning blueprint, full id (Infrastructure-1.0.0). Exactly one owner per entity. |
rtBlueprintLocked | bool | true = managed by blueprint, updates will overwrite; false = user-released. |
rtBlueprintAppliedAt | DateTime | UTC 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:
-
Declare the dependency in your
blueprint.yaml:blueprintDependencies:- HelloBlueprint-[1.0,2.0) -
Reference its CK models indirectly. You don't list them again — the dependency closure already imports them.
-
Add your own seed data. Your entities live in your blueprint's
rtBlueprintSourcenamespace; the entities fromHelloBlueprintremain owned by it. -
Apply normally. The engine resolves the transitive closure, applies dependencies first (topo-sorted), then your blueprint:
octo-cli -c InstallBlueprint -b ExtendedHello-1.0.0ListBlueprintInstallationsthen shows both blueprints —HelloBlueprintcarriesIsDependency: 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/OwnershipConflictduring apply. - If the dependent really needs to evolve those entities, ship a migration script that targets them by
rtIdor byrtWellKnownName + blueprintSourceOnly: true.
Uninstall and Refcounts
- Uninstalling
ExtendedHellodoes not removeHelloBlueprintif any other installed blueprint still depends on it (refcount > 0). octo-cli -c UninstallBlueprint -n ExtendedHello -ccascades: removesExtendedHelloand orphan-cleans transitively-depending blueprints whose refcount drops to zero.
Update Modes
| Mode | Behaviour |
|---|---|
Safe | Add new entities only. Existing entities are left alone, even if locked. |
Merge | Add new + upsert locked entities. Unlocked entities raise UserModified conflicts (default: skip). |
Full | Like Merge, plus delete entities that exist in the tenant but no longer in the seed. Unlocked → conflict. |
Migration | Execute 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.
| Type | Triggered when |
|---|---|
UserModified | The seed wants to update this entity, but the tenant entity has been unlocked. |
DeleteModified | Full 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:
| Resolution | Behaviour |
|---|---|
KeepUser | Keep the user's version, skip the blueprint change. |
KeepBlueprint | Apply the blueprint's version. UserModified: seed is re-applied and the entity is re-locked. DeleteModified (Full only): entity is erased. |
Merge | Currently treated as KeepUser (semantic 3-way merge is out of scope). |
Skip | Skip 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
| Action | Purpose |
|---|---|
Add | Insert an entity (data carries the full RtEntityTcDto payload). |
Update | Update attributes on matching entities (data is a { attributeName: value } dict). |
Delete | Erase matching entities (DeleteOptions.Erase — permanent). |
Rename | Rename an attribute on matching entities (shorthand for Transform of type Rename). |
Transform | Type-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:
| Interface | Purpose |
|---|---|
ITenantBlueprintInstallations | The current set of installed blueprints (one row per blueprint, with IsDependency flag). |
ITenantBlueprintHistory | Append-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
| Catalog | Description |
|---|---|
LocalFileSystemBlueprintCatalog | Loads blueprints from the file system. |
EmbeddedResourceBlueprintCatalog | Loads blueprints from assembly resources. |
PublicGitHubBlueprintCatalog | Reads blueprints from a public GitHub Pages site. |
PrivateGitHubBlueprintCatalog | Reads 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
- Small, focused blueprints. One blueprint per domain/feature. Compose via
blueprintDependencies, not by bundling unrelated entities. - Use version ranges for dependencies.
[1.0,)keeps things flexible; pinning exact versions is fine forblueprintIdbut rarely useful in dependencies. - Sparse seed data. Ship essential bootstrap data only — no test data, no per-customer specifics.
- Lock managed entities. Default
rtBlueprintLockedistrue; only override tofalsewhen you intend the user to take ownership immediately. - Migration scripts for breaking changes. Schema renames, deletes, and value transformations need an explicit script. Additive changes work via Merge alone.
- Test with dry-run.
UpdateBlueprint -drsimulates without persisting. - Keep backups. Default backup creation is on; only disable it when you have an alternate snapshot mechanism.
See Also
- CK Model Migrations — when the schema itself changes
- octo-cli — ListBlueprints and related blueprint commands (Install / Update / Rollback / Uninstall) — CLI commands for blueprint operations
- Studio: Blueprints — UI walkthrough in the Data Refinery Studio