Skip to content

feat(config): fold environment config into the environments list with explicit roles - #568

Merged
joshua-temple merged 4 commits into
mainfrom
feat/environments-fold
Jul 12, 2026
Merged

joshua-temple merged 4 commits into
mainfrom
feat/environments-fold

Conversation

@joshua-temple

Copy link
Copy Markdown
Collaborator

Problem

environments (an ordered []string) and environment_config (a map keyed by env name) were two separate top-level keys that had to be kept in sync, and promotion roles (release/prerelease) were purely positional, so reordering the list silently changed which environment published.

Fix

Fold the two into one environments list of objects:

  • Each entry is a bare string (- dev, sugar for a config-free environment) or a mapping (- {name: dev, wait_timer: 5, ...}) carrying that environment's inline settings. A custom UnmarshalYAML on the entry accepts both forms; a MarshalYAML collapses a config-free entry back to the bare string for a faithful round trip.
  • The top-level environment_config key is removed; it now reports as an unknown field with a did-you-mean pointing at environments.
  • List order still defines the promotion ladder. An optional role: (prerelease or release) overrides the positional default (last = release, second-from-last = prerelease). An unset role keeps the index-based behavior, so manifests without roles generate byte-identical output.
  • Component environments still whole-replace the inherited ladder (arrays replace under deep-merge), now carrying their inline settings with them, preserving the pre-fold slice-replace semantics.

Verification

  • go build, go test, go test -race, and golangci-lint all clean at the root; e2e module builds and vets clean.
  • The generate golden suite passes unchanged, confirming the fold is behavior-preserving for emitted workflows (env config reaches the same native-Environment and environment_url sinks).
  • New unit tests cover object-form parsing reaching the config accessors, bare-string sugar, environment_config rejection, unknown-entry-field rejection, duplicate/empty names, invalid/duplicate roles, role overriding the promotion graph, component subset whole-replace, and marshal round-trip.
  • Migrated every in-repo manifest, the JSON schema (three byte-identical copies), and the docs (reference/manifest.md, guides/environments.md, guides/components.md, reference/cli.md, security.md, internals/architecture.md); docs site builds.
  • Added e2e scenarios: 60-environments-folded-object (folded inline config reaching native deployment output) and 61-environment-role-override (explicit role: on a non-last environment).

… explicit roles

The separate environment_config map is folded into the environments list, which
becomes a list of objects: each entry is a bare string (- dev) for a config-free
environment or a mapping (- {name: dev, wait_timer: 5, ...}) carrying that
environment's inline settings. A custom UnmarshalYAML on the entry accepts both
forms; MarshalYAML collapses a config-free entry back to the bare string. The
top-level environment_config key is removed and now reports as an unknown field
with a did-you-mean pointing at environments.

List order still defines the promotion ladder. An optional role: (prerelease or
release) on an entry overrides the positional default (last = release,
second-from-last = prerelease); an unset role keeps the index-based behavior, so
manifests without roles are unchanged. Component environments still whole-replace
the inherited ladder, now carrying inline settings with them.

Migrates every in-repo manifest, the JSON schema (three byte-identical copies),
docs, and adds e2e scenarios for the folded object shape and the role override.

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
…d cascade path

The role: override was only partially honored: some paths used the role-aware
accessors while others still assumed the positional last/second-from-last, so the
two disagreed under a role override.

- The generated prod deploy job derived its target environment positionally
  (Environments[len-1]) while the runtime prod-deployment gate uses
  ReleaseEnvironment(); with role: release on a non-last env the job deployed the
  release SHA to the wrong native environment. Derive it from ReleaseEnvironment().
- The cascade promotion path derived prerelease/publish/prod positionally, so a
  dev-to-prod cascade disagreed with the role-aware single-step (default) path for
  the same manifest. Route it through PrereleaseEnvironment()/ReleaseEnvironment().
- The promote workflow's release-states comment now reads from the accessors
  rather than asserting position.

IsLastEnvironment stays a positional query (no production consumer) with a doc note
that release-stage decisions must use ReleaseEnvironment. Adds fail-before/pass-after
tests: the generated prod deploy job targets the role env, and default and cascade
modes agree on the release/prerelease markers for a role-annotated manifest. The
no-role baseline golden changes only in the reworded comment (env assignments
unchanged).

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
@github-actions

github-actions Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
Contributor

All PR Validation checks passed.

…nt change

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
…ents shape

The harness scenario Config is a direct alias of config.TrunkConfig, so
Config.Environments is now []EnvironmentEntry. The scenario parser handles the
folded shape correctly; only the test assertions comparing it to []string were
stale. Route them through EnvironmentNames(). All 99 scenarios parse under the
harness and every migrated scenario (29, 31, 60, 61) validates.

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
@joshua-temple
joshua-temple merged commit 451e3ec into main Jul 12, 2026
20 checks passed
@joshua-temple
joshua-temple deleted the feat/environments-fold branch July 12, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant