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 |
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:
| 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 — CLI commands for blueprint operations
- Studio: Blueprints — UI walkthrough in the Data Refinery Studio