Mutation Sources
Every Config Unit carries a record, called MutationSources, of which change last set each individual value in its configuration data. Where a Revision is a point-in-time snapshot of a unit's data and a Mutation records the source of one granular change, MutationSources is the accumulated result: a per-path index of who-and-what most recently touched each field. It serves two purposes — an audit trail ("git blame" for your configuration) and the information ConfigHub's merge engine uses to decide how to combine changes from different sources.
MutationSources is read-only in the sense that it is maintained automatically as the unit changes; you don't edit it the way you edit configuration data. You can, however, adjust the per-path protection flags it stores (see Protection, below).
What MutationSources records
MutationSources is organized to mirror the configuration data:
- One entry per resource. A unit's data is a collection of resources (for example, Kubernetes objects). MutationSources has one entry per resource, identified by type and name, plus a stable identifier that follows the resource across name changes and across variants. Both current and prior names are retained as aliases, so a resource can still be matched after it is renamed.
- A map of paths to changes. Within each resource entry is a map from configuration path (for example,
spec.replicas, or a specific container's image) to information about the change that last set that path: the kind of change (added, updated, or deleted), a reference to the Mutation (and therefore the Revision, function, link, trigger, or other source) that made it, and an eligibility flag described below.
Internally these structures are referred to as a ResourceMutationList (the list of per-resource entries) and, within each, a PathMutationMap (the per-path index). You can see them on a unit with:
cub unit get --space my-space my-unit -o jq=.Unit.MutationSources
Because ConfigHub treats configuration as data rather than text, paths address individual fields and individual elements of merge-keyed lists (such as a container matched by name), not line numbers. This is what lets ConfigHub attribute and merge changes field-by-field.
Viewing mutation sources
The raw -o jq form above is exact but unwieldy. For a readable view, cub unit get <unit> -o mutations renders a unit's MutationSources as a per-resource diff, grouped by the Protected flag so you can see at a glance which fields are protected and which a merge may overwrite:
- Locally overridden (preserved during merges) — paths whose Protected is
true. - Eligible for upstream merges — paths whose Protected is
false.
cub unit get --space my-space my-unit -o mutations
Add --verbose to also show each field's value, which change last set it (function, merge source, link, or trigger), and a summary table of the contributing Mutations.
The same -o mutations flag previews a pending change before it is written — for example, the per-path effect of an upgrade — when added to cub unit update ... --dry-run; see the variants guide.
How merging uses MutationSources
When ConfigHub merges changes between related units — propagating an upstream change into a cloned variant via Upgrade, merging a range of changes from another unit, or resolving a link — it needs to tell two kinds of changes apart: changes that came from the source being merged, versus changes made independently in the target unit. The latter are local overrides and are preserved by default.
MutationSources is what makes this distinction possible. Because each path records which mutation last set it, the merge engine can recognize that, say, the downstream unit's replica count was set by a local function invocation rather than inherited from upstream, and therefore should not be overwritten by an upgrade.
There are two complementary mechanisms that use this information to protect local overrides:
- A filter over mutation history (
WhereMutation). The Upgrade Link created when you clone a unit carries a defaultWhereMutationthat treats only changes originating from the clone or a prior upgrade/merge as eligible to be overwritten. Anything else — a hand edit, a function invocation, a needs/provides binding, a trigger — is preserved. - The stored Protected flag on each path (used when no
WhereMutationis supplied).
For the full description of the merge algorithm, the Upgrade and merge commands, and how overrides are detected and preserved, see the Creating and managing variants guide.
Protection
Each path in MutationSources carries a boolean Protected that records whether the path is a local override a merge must not overwrite:
true— the value is a local override (set by a hand edit, function, trigger, or needs/provides resolution) and is protected from being overwritten.false— the value came from somewhere else: a clone, an upgrade, a merge, or content merged in from an external source; a future merge may overwrite it.
ConfigHub maintains these flags automatically as a unit changes. They matter most when a merge's override-preservation subtraction step is turned off (see the variants guide): in that mode, the stored Protected flags are the sole mechanism deciding which paths a merge may overwrite.
You can adjust the flags directly to change which fields a future merge is allowed to overwrite — for example, to protect a field that would otherwise be eligible, or to re-open one you previously protected:
# Protect the downstream replica count from being overwritten by merges.
cub unit set-protection my-unit \
--protect "apps/v1/Deployment:my-namespace/my-app:spec.replicas"
Re-open a path with --unprotect instead. Either way, a new Revision is produced only if a flag actually changes.
An operation that is applying a value chosen somewhere else — replaying a change, copying an already-reviewed value, a script propagating a release — can decline to record ownership of what it writes, with --preserve-protection on cub unit update, cub function set, and cub run:
cub unit update --space my-space my-unit ./data.yaml --preserve-protection \
--change-desc "Replay the upstream image bump"
Each path it writes keeps the flag it already had, and a path with no history is left unprotected. Without it, such an operation records upstream content as a local override, which silently blocks the next merge from updating that path. It can only narrow what an operation claims, never widen it: a path you protected stays protected.
Provenance and audit
Independently of merging, MutationSources answers "what last changed this field, and why?" Each path points at the Mutation responsible, which in turn carries the source — an update, a function invocation (ad hoc, triggered, or part of a clone/upgrade), a link resolution, an import, and so on. Restoring a unit to a prior Revision restores its MutationSources too, so the provenance always reflects the state you restored.