Skip to content

plugin-migrate

Placement compiled-in (in-process)
Source github.com/opencharly/charly/candy/plugin-migrate
Version 2026.186.0100
Candy plugin-migrate

This plugin is listed in charly/charly.yml’s compiled_plugins:, so its providers are compiled into the charly binary and register in-process.

The reserved words this plugin serves:

  • migrate — command class

COMPILED-IN charly plugin owning the config-schema MIGRATION ENGINE (M15 — migrate OUT of core). It serves ONE capability:

  • command:migrate — the operator charly migrate command AND the in-proc engine charly’s remote-cache auto-migration (refs.go EnsureRepoDownloaded) invokes with OpRun –project-only. The CUE-anchored declarative migration table + the generic op-walker (rename_key / delete_key / remap_scalar / move_key) + the file-walk drivers (engine.go) — the ~600 LOC formerly in charly/migrate_engine.go + migrate.go + migrate_cmd.go — live HERE now. The migration DATA (migrations.cue) is embedded here; #Migration is read from the SDK schema (sdk/schema — migration.cue + version.cue, since #Migration.version pins to the SDK’s single-source #CanonCalVer, which never leaves sdk/schema). The below-floor/behind-head load-gate HINTS (the “run charly migrate” messages) stay core — they only point at this command.

PLACEMENT — COMPILED-IN (listed in the embedded charly/charly.yml compiled_plugins:). Migrate is whole-project file-based and must run when the config is EXACTLY what cannot load, so it can never be discovered out-of-process nor run on root bytes inside LoadUnified. Compiled-in command placement gives command:migrate a registry word at init() independent of any config, so the refs.go auto-migration resolves it in-proc deep in config loading. The cmd/serve binary backs the out-of-process placement (a consumer that does not compile migrate in).

R10: the disposable check-migrate-local bed (a kind:local deploy composing this candy) proves the compiled-in command:migrate resolves + Invoke(OpRun) runs the engine end-to-end: (a) at-HEAD no-op, (b) –dry-run flag plumbing, (c) below-floor refusal. The declarative transform ops are covered by the plugin’s engine_test.go unit tests.

The CUE schema below is the authoritative grammar for this plugin’s input. It is the same single source that generates the plugin’s Go parameter types and answers the runtime Describe RPC, so this page cannot disagree with either.

// migration.cue — the SCHEMA for the declarative migration table (the DATA lives
// beside it in candy/plugin-migrate/migrations.cue). This file lives IN the plugin,
// NOT sdk/schema: #Migration is validation-only, consumed ONLY by this plugin's
// engine and by no charly core code — so per the kernel/plugin boundary law
// (/charly-internals:plugin) a plugin-only schema lives in its plugin, never the SDK
// contract. The engine embeds it (//go:embed schema/migration.cue) and unifies each
// table entry against #Migration at process start (fail-fast, like registerCueKind).
//
// #Migration.version pins to #CanonCalVer, which STAYS the SDK's single source of
// truth (sdk/schema/version.cue) — the engine concatenates THIS file with the SDK's
// version.cue to compile #Migration standalone, without duplicating #CanonCalVer and
// without pulling charly's full ingress schema. The defs are @go(-) (no gengotypes
// type); the "exactly one of ops/apply" rule and the CalVer ordering are enforced in
// Go (engine.go) — CUE's field-presence comparison is too fragile for that gate.
// One migration step: a CalVer, a label, and EITHER a list of declarative ops OR
// a named Go escape-hatch hook.
#Migration: {
version: #CanonCalVer // CalVer this step lands files at; engine runs version > file-version, ascending
name: string // short label for --dry-run / progress
touches_host?: bool | *false // also rewrite the per-host overlay?
ops?: [...#MigrationOp] // declarative ops, applied in order (mutually exclusive with apply)
apply?: string // named Go hook for a structural reshape (mutually exclusive with ops)
} @go(-)
// Where an op applies: only the top-level mapping (root) or recursively (any).
#Scope: "root" | "any" @go(-)
// The declarative op vocabulary — one closed arm per op, discriminated by `op:`.
#MigrationOp: #RenameKey | #DeleteKey | #RemapScalar | #MoveKey @go(-)
// Rename a mapping key, preserving its value + comments.
#RenameKey: close({op: "rename_key", from: string, to: string, scope: #Scope | *"any", under_kind?: string}) @go(-)
// Delete a mapping key/value pair.
#DeleteKey: close({op: "delete_key", key: string, scope: #Scope | *"any", under_kind?: string}) @go(-)
// Remap a scalar value under a key (e.g. target: host -> target: local).
#RemapScalar: close({op: "remap_scalar", key: string, from: string, to: string, under_kind?: string}) @go(-)
// Relocate a key/value pair from one child mapping to another (simple reparent).
#MoveKey: close({op: "move_key", key: string, from_parent: string, to_parent: string, under_kind?: string}) @go(-)

See also the candy reference for this candy’s install surface.