fix(config): save_config must round-trip role + secrets block

save_config rewrites castle.yaml from scratch and only re-emitted gateway/repo/
roots — so any config save (dashboard deployment edit, config_editor) silently
STRIPPED top-level 'role' and the 'secrets:' backend block. Effect: the node
reverted to a file-backend follower, the vault stopped resolving, and a
subsequent apply baked <MISSING_SECRET> into service env (hit castle-lakehouse).

Now re-emit role (from config) + preserve the secrets block (read from the
existing file, since it's not modeled on CastleConfig). Regression test added.
This commit is contained in:
2026-07-07 08:22:28 -07:00
parent 01d0abd5ed
commit 1bc41ddd2d
2 changed files with 34 additions and 0 deletions

View File

@@ -680,6 +680,19 @@ def save_config(config: CastleConfig) -> None:
n: s.model_dump(exclude_none=True, exclude_defaults=True)
for n, s in config.agents.items()
}
# MUST round-trip (save rewrites from scratch): the fleet role and the
# `secrets:` backend block are not otherwise re-emitted, so they'd be silently
# dropped on the next save — reverting the node to a follower on the file
# backend. `role` lives on the config; `secrets` isn't modeled, so preserve it
# from the existing file.
if config.role and config.role != "follower":
data["role"] = config.role
try:
existing = yaml.safe_load((config.root / "castle.yaml").read_text()) or {}
if existing.get("secrets"):
data["secrets"] = existing["secrets"]
except Exception:
pass
config_path = config.root / "castle.yaml"
with open(config_path, "w") as f: