Problem
Every state-write path (cascade reset --state, and the orchestrate / promote / finalize / hotfix / rollback state writers) loads the manifest, mutates the typed CICDFile/TrunkConfig struct, and re-marshals the whole struct back to YAML. Any manifest field the running binary's struct does not model is therefore silently removed on the next state write.
This was surfaced by the fleet coverage work: a cascade reset --state run with a binary built before drift_check was added to the struct (#236) deleted the drift_check opt-in from cascade-example-4env's manifest (commit message chore: reset state for testing [skip ci], a 117-line manifest deletion that took out both the state tree and the unmodeled config block). All currently shipped config fields are modeled, so a current binary round-trips correctly, but the pattern is a latent landmine.
Impact
An adopter whose manifest uses a field newer than their installed cascade version loses that config on every state write (orchestrate, promote, finalize, rollback), with no error. The output is a quietly downgraded manifest.
Suggested hardening
Preserve unknown keys on the state-write round-trip rather than marshaling only the typed struct (for example, parse into a yaml.Node, mutate only the state subtree, and re-emit, so unmodeled keys survive). Needs an e2e scenario per CONTRIBUTING that adds a manifest key the test binary does not model and asserts it survives a state write.
Workaround
Keep the fleet and operator cascade binaries at or above the manifest schema in use (>= #236 for drift_check).
Problem
Every state-write path (
cascade reset --state, and the orchestrate / promote / finalize / hotfix / rollback state writers) loads the manifest, mutates the typedCICDFile/TrunkConfigstruct, and re-marshals the whole struct back to YAML. Any manifest field the running binary's struct does not model is therefore silently removed on the next state write.This was surfaced by the fleet coverage work: a
cascade reset --staterun with a binary built beforedrift_checkwas added to the struct (#236) deleted thedrift_checkopt-in fromcascade-example-4env's manifest (commit messagechore: reset state for testing [skip ci], a 117-line manifest deletion that took out both the state tree and the unmodeled config block). All currently shipped config fields are modeled, so a current binary round-trips correctly, but the pattern is a latent landmine.Impact
An adopter whose manifest uses a field newer than their installed cascade version loses that config on every state write (orchestrate, promote, finalize, rollback), with no error. The output is a quietly downgraded manifest.
Suggested hardening
Preserve unknown keys on the state-write round-trip rather than marshaling only the typed struct (for example, parse into a
yaml.Node, mutate only the state subtree, and re-emit, so unmodeled keys survive). Needs an e2e scenario per CONTRIBUTING that adds a manifest key the test binary does not model and asserts it survives a state write.Workaround
Keep the fleet and operator
cascadebinaries at or above the manifest schema in use (>= #236 fordrift_check).