From a37b5caa6a9c0b946179539566d92918c86831f8 Mon Sep 17 00:00:00 2001 From: Romamo Date: Fri, 2 Oct 2026 12:42:21 +0300 Subject: [PATCH] fix: treaty 1.0.0rc18; reports and snapshots survive cleanup rc18 fixes treaty #227, where cleanup --confirm-destructive could delete paths it should keep, such as a declared path spelled in another case on macOS. Cloudfall declares side effects under {project_root}/tmp/ since rc13, so its cleanup was exposed. The dashboard, the domain observations and observe's server snapshots are now SideEffect "output" (rc17): cleanup never removes them, which matters for the snapshots audit reads. Only the regenerated inventory overlay stays "cache". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015MJdwagp7FxuuxminHgv2V --- CHANGELOG.md | 7 ++++--- pyproject.toml | 2 +- sdk/src/cloudfall/app.py | 10 +++++----- uv.lock | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d8f064..cd7b100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,7 +90,7 @@ Notable changes to Cloudfall. The format follows treaty #197). The plan writes the result keys as `null`, and the result writes the plan keys (`wouldRun`, `instruction`) as `null`, since treaty answers one object per command. `observe` is read-only (it declares the - snapshots it writes under the project) and exits 91 `INCOMPLETE` (was 1) + snapshots it writes under the project as output, kept by `cleanup`) and exits 91 `INCOMPLETE` (was 1) with the run in `data` when a server gave no snapshot. Engine steps, sops and the Render API go through treaty: `doctor` checks sops, and `import render-api` honours `--proxy` and marks its answer as content @@ -127,7 +127,8 @@ Notable changes to Cloudfall. The format follows with the same `data` keys, plus `effect: created` on `init` and `add`, which treaty requires of a command that changes the project. `dashboard build` and `services inspect` stay read-only and declare the - reports they regenerate under `tmp/`. Failures: 6 `CONFLICT` for a resource that already + reports they write under `tmp/` as their output, which treaty's `cleanup` + never removes. Failures: 6 `CONFLICT` for a resource that already exists (was 2), 7 `PERMISSION_DENIED` for a read-only project or path (was 1 or 2), 4 `PRECONDITION` for a non-empty `init` directory (was 2), 5 `NOT_FOUND` for a missing key file, 2 `ARG_ERROR` for a bad id, name or @@ -154,7 +155,7 @@ Notable changes to Cloudfall. The format follows is refused, since treaty's `--output` names a file. Relative paths still resolve against the project, without changing the working directory - `cloudfall-engine` runs Ansible and git through treaty's `ctx.run` - (treaty 1.0.0rc14), so `--timeout` and Ctrl-C stop the whole process + (treaty 1.0.0rc18), so `--timeout` and Ctrl-C stop the whole process group, and secrets are redacted from the play log. The play log streams to stderr as plain text, as before, terminal or not; `--quiet` silences it. A failed playbook carries the last 4096 characters of the log in diff --git a/pyproject.toml b/pyproject.toml index 10749a7..6a19c63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "ansible-core>=2.21,<2.22", "jsonschema[format]>=4.23,<5", "pyyaml>=6,<7", - "treaty==1.0.0rc14", + "treaty==1.0.0rc18", ] [project.urls] diff --git a/sdk/src/cloudfall/app.py b/sdk/src/cloudfall/app.py index 5f0475d..8ea68fa 100644 --- a/sdk/src/cloudfall/app.py +++ b/sdk/src/cloudfall/app.py @@ -1304,7 +1304,7 @@ class DashboardPayload(Payload): timeout=120, supports_raw_payload=True, project_root=PROJECT_MARKERS, - filesystem_side_effects=[SideEffect("{project_root}/tmp/dashboard/", "cache")], + filesystem_side_effects=[SideEffect("{project_root}/tmp/dashboard/", "output")], exit_codes=["PROJECT_INVALID", "CONFIG_INVALID", "PERMISSION_DENIED"], examples=[ ( @@ -1362,7 +1362,7 @@ class InspectedPayload(Payload): timeout=600, project_root=PROJECT_MARKERS, filesystem_side_effects=[ - SideEffect("{project_root}/tmp/observed-services/", "cache") + SideEffect("{project_root}/tmp/observed-services/", "output") ], exit_codes=["PROJECT_INVALID", "CONFIG_INVALID", "PERMISSION_DENIED"], examples=[("Probe every declared domain", "cloudfall services inspect")], @@ -2323,12 +2323,12 @@ def run(argv: Sequence[str], environment: Mapping[str, str]) -> PlaybookRun: "Collect read-only server snapshots from the fleet; servers without one " "exit non-zero with the run in data" ), - # It only reads the hosts; its snapshots and the inventory overlay are - # regenerated under the project on every run. + # It only reads the hosts. The snapshots are its product, which audit + # reads, so cleanup keeps them; the inventory overlay is regenerated. danger_level="safe", project_root=PROJECT_MARKERS, filesystem_side_effects=[ - SideEffect("{project_root}/tmp/observed/", "cache"), + SideEffect("{project_root}/tmp/observed/", "output"), SideEffect("{project_root}/tmp/cloudfall/", "cache"), ], exit_codes=[ diff --git a/uv.lock b/uv.lock index 2d0da73..4afac3b 100644 --- a/uv.lock +++ b/uv.lock @@ -285,7 +285,7 @@ requires-dist = [ { name = "jsonschema", extras = ["format"], specifier = ">=4.23,<5" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=2,<3" }, { name = "pyyaml", specifier = ">=6,<7" }, - { name = "treaty", specifier = "==1.0.0rc14" }, + { name = "treaty", specifier = "==1.0.0rc18" }, ] provides-extras = ["mcp"] @@ -1085,11 +1085,11 @@ wheels = [ [[package]] name = "treaty" -version = "1.0.0rc14" +version = "1.0.0rc18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/20/81f1ed3c12939a83d78651fbe82ce89903b05c9079cc7e88e4417ba17e9e/treaty-1.0.0rc14.tar.gz", hash = "sha256:26498a8dc0c163e46bdf3c1fc1fe7c144638b1798cad14742f8efde13763f49e", size = 513570, upload-time = "2026-10-02T01:44:36.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/d0/3c17b51f57328feab7b65eeea18f333a876436d52216a17df7f7300285f0/treaty-1.0.0rc18.tar.gz", hash = "sha256:33defd5ede0e9568eaf5a34dc759422483cc508ea9368b90b61cb8120125018c", size = 519893, upload-time = "2026-10-02T09:36:27.466Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/c6/f377b673fde544a69305646674d6cdd234455285336a33abcce3dee3879a/treaty-1.0.0rc14-py3-none-any.whl", hash = "sha256:3178710be0b507d4d7a481db11326e2c94c29aa8c2633db1645d8e4c67a2f28e", size = 515200, upload-time = "2026-10-02T01:44:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/4e/47/07abe2d4f97af626eeb4628032a2b0927ac2709d557669bcb3fb957ae7e5/treaty-1.0.0rc18-py3-none-any.whl", hash = "sha256:bdd353f80aeafc46fa8d12a34cbdff7007cf95f2087825076f0e724829854073", size = 520884, upload-time = "2026-10-02T09:36:25.96Z" }, ] [[package]]