Carry the environment on a config write - #593
Merged
Merged
Conversation
`controlplane.Database` took a config write as (app, key, value) and a removal as (app, key). Both are made IN an environment: `Engine.SetConfig` and `Engine.UnsetConfig` resolve one, and re-apply exactly that environment's workload once the store holds the value. The environment stopped at the seam, so an implementation of `Database` saw a staging change and a production one as the same call, with nothing to act on per-environment. Add `env` to the two writes, positioned after the app as every other environment-carrying method on the seam positions it, and pass the canonical name (ADR-0067 §2) so an implementation never has to know that an unnamed environment and "prod" are the same one. Leave the READ app-global. `AppEnv` takes no environment because the store has none: the `app_env` table is keyed by (app, key) with no environment column, docs/CAPABILITIES.md states that a config value applies in every environment, and deploy, rollback, a re-apply and a one-off command all render a workload from that one read. An environment on `AppEnv` would not carry information — it would narrow what a running app sees to the values that happened to be written while pointed at the environment it is deployed to, and silently drop the rest. Postgres therefore takes the parameter and does not store it, and says so where a reader of the row would ask. The fake records each write with its environment, in a log kept apart from the config it stores, so a test can assert the environment reached the seam while the fake stays exactly as app-global as Postgres is. Signed-off-by: Nicholas Phillips <nsphilli@gmail.com>
incognick
added a commit
that referenced
this pull request
Aug 20, 2026
* Key an app's config on the environment The `app_env` table was keyed (app, key) with no environment column, so an app had exactly one set of config values and a deploy rendered that one set into whichever environment it targeted. Config was the one part of an app that could not differ between staging and production: an app needing a different API endpoint, log level or feature flag per environment had nowhere to say so, and a value set while trying something in staging reached production on that app's next deploy there. Migration 00037 adds the environment column and moves the primary key to (app, environment, key), backfilling every existing row to the default environment `prod` — the only environment an install that never added a second one has, and therefore the one those values were already being rendered into. PR #593 had already put the environment on the two writes as provenance; it is now part of what identifies the row, and the read takes it too. `Database.AppEnv` gains the environment, positioned after the app as every other environment-carrying method on the seam positions it. The five call sites each pass the environment they are acting on: deploy and run render the environment they target, rollback renders the environment being rolled back, a re-apply rolls the environment whose config was written, and `ListConfig` lists the environment asked for. There is deliberately no wildcard scope and no resolution order — a read returns that environment's rows and nothing else — so the comments at those sites, which asserted the app-global read, are rewritten to state what is now true rather than left as a trap. The direct consequence is that a newly added environment starts with no config at all, and an app deployed into one comes up on its image's own defaults. That is stated where a user looks: the environments section and the config table in docs/CAPABILITIES.md (whose "the same values apply in every environment" promise is now false and is replaced), the `app config` command help, and the empty listing, which now names the environment it is empty in. The store test asserts both directions of isolation against a real database — the same key in two environments holds two values, and a removal in one leaves the other alone — and the engine tests assert the read path where it decides what a running app sees: a deploy into staging renders staging's value and not the default environment's, a deploy into the default environment still renders its own, and a fresh environment renders nothing. Signed-off-by: Nicholas Phillips <nsphilli@gmail.com> * Correct the comments and CLI help that still say config is app-global Four comments and two agent command descriptions outlived the schema change in the commit before this one: the seam's SetAppEnv and UnsetAppEnv still told an implementer the write lands everywhere, DeployRequest still explained Env's absence by an app-global store, the fake still justified its storage shape by matching one, and burrow-agent's deploy and build help still told an agent operator that config applies across environments. A comment asserting a property is a hypothesis, and one asserting the property the code no longer has is worse than none — it is what the next reader trusts instead of reading the query. Each now states what is true: the write lands in the environment it was made in, the read takes an environment, and an agent sets config in the environment it is deploying to. Signed-off-by: Nicholas Phillips <nsphilli@gmail.com> --------- Signed-off-by: Nicholas Phillips <nsphilli@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
envtocontrolplane.Database.SetAppEnvandUnsetAppEnv. An embedder implementing theDatabaseseam cannot tell which environment a config write belongs to, and needs to in order to act per-environment on one — the engine knows (it re-applies that environment's workload straight afterwards) and used to drop the fact at the seam.The read stays app-global and nothing an app sees changes:
AppEnvcarries no environment because the store has none, so narrowing it would be a schema change and a change of meaning rather than a record of one. Postgres accepts the parameter and does not store it.