Meridian handbook
Getting started

Upgrading the engine

How to move a project to a new engine version, covering engine.version, the template contract and check --contract-report, migrations as an explicit deploy step, and the order to run things in.

Upgrading Meridian means running a new binary (or image) against your project and database. Your project files do not change unless the release notes say so. Three things tie a project to an engine: the engine major version it targets, the template contract its templates are written against, and the database migrations the engine has applied.

What ties a project to an engine

Where it isWhat checks it
The engine major versionengine.version in config/engine.yaml, such as "1.x"engine-dmo check and every boot
The template contracttemplate.contract in config/engine.yaml, 1 when unsetengine-dmo check and every boot
The engine’s own tablesThe database, recorded per migrationengine-dmo migrate status, every boot, the readiness check

engine-dmo version prints the binary’s version and the template contract it implements:

meridian v0.8.0 (vips) go1.27.3
  ...
  template contract 1

engine.version

engine:
  version: "1.x"

engine.version names the engine major version the project is written for, as a major number followed by .x. Any other form is an error. A release binary from 1.0.0 on also refuses a project whose major differs from its own, with the error at this line and a hint to run the matching engine or upgrade the project and change the value. Releases before 1.0.0 check only the form, so on a 0.x engine the starter’s "1.x" is accepted and the value protects nothing yet; it starts to matter at 1.0.0.

The template contract

The template contract is the versioned surface your templates use: the context, the globals, the functions and the filters. template.contract in engine.yaml selects it; it is 1 when unset, and 1 is the only contract this engine supports, so any other value is an error:

config/engine.yaml:23:13: template.contract 2 is not supported by this engine; supported: 1

Before an engine supports a new contract, anything the next contract removes is reported as deprecated. List every such use in your templates, each with the form to write instead:

engine-dmo check --no-db --contract-report
contract report: nothing the next contract version removes

Work through the list before you raise template.contract, then run engine-dmo check again.

Migrations are a deploy step

A new engine version usually brings migrations: changes to the engine’s own tables, separate from your schema. They are numbered per stream (core, and each module the binary compiles), they only move forward, and they run when you say so:

engine-dmo migrate status   # applied and pending; exits 2 if any are pending
engine-dmo migrate up       # apply every pending migration
engine-dmo migrate to 12    # apply up to and including core version 12
engine-dmo migrate to crm/3 # or up to a module's version 3

By default the server refuses to start while a migration is pending. Two settings change that, and neither is recommended in production because they hide the migration inside a restart:

SettingEffect
engine-dmo serve --migrateApply pending migrations, then serve, for this run
migrate.on_boot: apply in engine.yamlApply pending migrations on every boot. The default is refuse

migrate up refuses a database whose encoding is not UTF8, and refuses to continue if an applied migration’s file has changed since it ran. Migrations have no down step: to go back after one, restore the database backup you took before it.

engine-dmo health --readiness exits 0 only when startup is complete, the database answers and every migration the binary knows is applied, so a load balancer or deploy step never sends traffic to a process whose tables are not ready. --wait 60s keeps trying for that long.

Schema drift at boot

At boot the engine also compares your configuration with the schema in the database. What it does with a difference is schema.on_drift:

ValueEffect
refuseRefuse to start and print the plan. The default
applyApply the plan at boot; a destructive plan still needs its approval file
warnLog the difference and start without applying it. Refused in production

Run engine-dmo schema apply as its own step, after the migrations, so a schema change is planned and reviewed like any other.

Upgrade a project

The order matters: the new binary checks the project before anything changes, the backup comes before the migrations, and the new version serves only once its tables and schema are ready.

flowchart LR
  notes["Read the<br/>release notes"] --> check["check --no-db<br/>with the new binary"]
  check --> backup["Back up the<br/>database and media"]
  backup --> migrate["migrate up"]
  migrate --> schema["schema plan<br/>and apply"]
  schema --> start["Start serve and wait<br/>for health --readiness"]
  start --> refresh["Refresh editor schemas,<br/>AGENTS.md and skills"]

Read the release notes

They list the migrations the release brings and anything you must change.

Check the project against the new binary

engine-dmo version
engine-dmo check --no-db --contract-report

Fix every error before going further. Run it in CI with the new image, so a project that does not pass never reaches a server.

Back up the database

Take a backup you have restored before. Migrations cannot be undone any other way. The engine has no backup command: back up the database with pg_dump and, with the filesystem storage driver, copy the storage directory beside it (Backups and restore).

Migrate, then apply the schema

engine-dmo migrate up
engine-dmo schema plan
engine-dmo schema apply

Run these with the new binary, as one-off steps, before the new version starts serving.

Start the new version

Start engine-dmo serve with the new binary and wait for engine-dmo health --readiness.

Refresh the generated files

engine-dmo config schema --write            # the editor schemas in .meridian/schema
engine-dmo guide --out AGENTS.md            # the project guide, which names the engine version
engine-dmo docs skill --out .claude/skills/meridian/SKILL.md

The meridian skill is the handbook’s agents page from the binary that wrote it, so an agent reading it follows the engine you now run. Any engine-dmo dev verb also rewrites the agent files a project has.

In a container deployment, run the migration and schema steps as one-off containers of the new image before the engine container starts. Single-node deployment has a Compose file that does exactly this, Upgrades covers rolling upgrades and rolling back, and Backups and restore covers backups and restores.

Extensions

An extension is a Go plugin, and it opens only in an engine built with the same Go toolchain, platform, build flags and meridian-extend version; the engine refuses a mismatch and names it. engine-dmo version prints what the engine was built with on its extensions: line. Rebuild your own extensions for each engine upgrade; the first-party extensions ship inside the -vips image and always match it. See Extending.