fix(config): scoped (PATCH) writes for deployment/program edits
Root-cause hardening for the dropped-globals bug: config edits went through the full-document save_config, which rewrote castle.yaml globals (+ every resource file) on every deployment/program edit — so an unmodeled global (role, secrets) was dropped. - add write_deployment_file / write_program_file: persist ONE resource file, never touching globals or other resources - config_editor deployment/program save/delete/enable now use those instead of save_config, so a deployment edit can't rewrite castle.yaml - save_config now preserves ANY unmanaged top-level global (not just secrets) - _aggregate_yaml surfaces role + secrets (raw-yaml round-trip completeness) - tests: scoped write leaves globals byte-identical; unmanaged global survives Verified live: editing lakehouse via the endpoint leaves castle.yaml unchanged.
This commit is contained in:
@@ -52,6 +52,40 @@ def test_save_config_round_trips_role_and_secrets(tmp_path: Path) -> None:
|
||||
assert reloaded.get("secrets", {}).get("backend") == "openbao"
|
||||
|
||||
|
||||
def test_save_config_preserves_arbitrary_unmanaged_global(tmp_path: Path) -> None:
|
||||
"""Any top-level key save_config doesn't model must survive a rewrite."""
|
||||
from castle_core.config import load_config, save_config
|
||||
|
||||
(tmp_path / "castle.yaml").write_text(
|
||||
yaml.safe_dump(
|
||||
{"gateway": {"port": 18000}, "role": "authority", "future_thing": {"x": 1}}
|
||||
)
|
||||
)
|
||||
save_config(load_config(tmp_path))
|
||||
reloaded = yaml.safe_load((tmp_path / "castle.yaml").read_text())
|
||||
assert reloaded.get("future_thing") == {"x": 1}
|
||||
assert reloaded.get("role") == "authority"
|
||||
|
||||
|
||||
def test_write_deployment_file_leaves_globals_untouched(castle_root: Path) -> None:
|
||||
"""A scoped deployment write must not rewrite castle.yaml globals (the PATCH
|
||||
guarantee that stops a deployment edit from dropping role/secrets)."""
|
||||
from castle_core.config import load_config, write_deployment_file
|
||||
|
||||
cy = castle_root / "castle.yaml"
|
||||
data = yaml.safe_load(cy.read_text())
|
||||
data["role"] = "authority"
|
||||
data["secrets"] = {"backend": "openbao"}
|
||||
cy.write_text(yaml.safe_dump(data))
|
||||
before = cy.read_text()
|
||||
|
||||
config = load_config(castle_root)
|
||||
kind, name, _dep = next(iter(config.all_deployments()))
|
||||
write_deployment_file(config, kind, name)
|
||||
|
||||
assert cy.read_text() == before # globals byte-identical — nothing touched them
|
||||
|
||||
|
||||
def test_registry_role_round_trip(tmp_path: Path) -> None:
|
||||
reg = NodeRegistry(
|
||||
node=NodeConfig(hostname="civil", role="authority"), deployed={}
|
||||
|
||||
Reference in New Issue
Block a user