cloudfall 0.7.0rc5__tar.gz → 0.8.0rc2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/CHANGELOG.md +35 -1
  2. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/PKG-INFO +2 -1
  3. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/README.md +1 -0
  4. cloudfall-0.8.0rc2/changelog.d/89-ci-deploy-guide.md +3 -0
  5. cloudfall-0.8.0rc2/changelog.d/96-healthcheck-none.md +3 -0
  6. cloudfall-0.8.0rc2/changelog.d/97-stale-proposals.md +3 -0
  7. cloudfall-0.8.0rc2/docs/ci-deploy-guide.md +385 -0
  8. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/getting-started.md +3 -0
  9. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/specs/001-stale-proposals.md +13 -1
  10. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/defaults/main.yml +3 -0
  11. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/meta/argument_specs.yml +3 -0
  12. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/tasks/main.yml +80 -1
  13. cloudfall-0.8.0rc2/engine/tests/test_deploy_role.py +203 -0
  14. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/pyproject.toml +1 -1
  15. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/app.py +70 -10
  16. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/decision.py +108 -0
  17. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/why.py +43 -9
  18. cloudfall-0.8.0rc2/sdk/tests/test_stale.py +384 -0
  19. cloudfall-0.7.0rc5/changelog.d/76-changed-from-play-stats.md +0 -10
  20. cloudfall-0.7.0rc5/changelog.d/78-why-agent-turn.md +0 -13
  21. cloudfall-0.7.0rc5/changelog.d/81-database-claims.md +0 -8
  22. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/.ansible-lint +0 -0
  23. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/.gitignore +0 -0
  24. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/ARCHITECTURE.md +0 -0
  25. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/CODE_OF_CONDUCT.md +0 -0
  26. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/CONTRIBUTING.md +0 -0
  27. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/LICENSE +0 -0
  28. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/MANIFESTO.md +0 -0
  29. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/ROADMAP.md +0 -0
  30. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/SECURITY.md +0 -0
  31. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/Taskfile.yml +0 -0
  32. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/changelog.d/README.md +0 -0
  33. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/README.md +0 -0
  34. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/alert-rules/postgresql-down.yaml +0 -0
  35. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/applications/crm.yaml +0 -0
  36. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/components/crm-backend.yaml +0 -0
  37. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/domains/crm-site.yaml +0 -0
  38. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/logging-stacks/operations.yaml +0 -0
  39. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/operator-policies/production.yaml +0 -0
  40. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/server-types/debian-application.yaml +0 -0
  41. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/servers/h1.yaml +0 -0
  42. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/servers/h2.yaml +0 -0
  43. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/services/postgresql-main.yaml +0 -0
  44. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/services/redis-cache.yaml +0 -0
  45. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/examples/ssh-public-keys/example-admin.yaml +0 -0
  46. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/agent-investigation.schema.json +0 -0
  47. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/alert-rule.schema.json +0 -0
  48. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/application.schema.json +0 -0
  49. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/artifact.schema.json +0 -0
  50. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/backup-receipt.schema.json +0 -0
  51. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/common.schema.json +0 -0
  52. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/component.schema.json +0 -0
  53. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/deployment-receipt.schema.json +0 -0
  54. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/domain.schema.json +0 -0
  55. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/environment-receipt.schema.json +0 -0
  56. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/logging-stack.schema.json +0 -0
  57. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/observed-domain.schema.json +0 -0
  58. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/observed-server.schema.json +0 -0
  59. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/operation-decision.schema.json +0 -0
  60. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/operation.schema.json +0 -0
  61. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/operator-policy.schema.json +0 -0
  62. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/operator-proposal.schema.json +0 -0
  63. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/release-receipt.schema.json +0 -0
  64. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/server-type.schema.json +0 -0
  65. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/server.schema.json +0 -0
  66. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/service.schema.json +0 -0
  67. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/schemas/v1/ssh-public-key.schema.json +0 -0
  68. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/invalid/missing-server/applications/crm.yaml +0 -0
  69. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/invalid/missing-server/components/crm-backend.yaml +0 -0
  70. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/invalid/missing-server/server-types/debian-application.yaml +0 -0
  71. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/invalid/missing-server/servers/h1.yaml +0 -0
  72. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/invalid/schema/components/component-unknown-field.yaml +0 -0
  73. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/observed/compliant/h1.json +0 -0
  74. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/config/tests/observed/compliant/h2.json +0 -0
  75. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/README.md +0 -0
  76. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/.gitignore +0 -0
  77. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/ansible.cfg +0 -0
  78. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/inventories/production/hosts.yml +0 -0
  79. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/converge.yml +0 -0
  80. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/disk-report.yml +0 -0
  81. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/journal-vacuum.yml +0 -0
  82. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/postgresql-reinit.yml +0 -0
  83. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/postgresql-restart.yml +0 -0
  84. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/service-logs.yml +0 -0
  85. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/operations/shop-logrotate.yml +0 -0
  86. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/disk-report.yml +0 -0
  87. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/journal-vacuum.yml +0 -0
  88. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/postgresql-reinit.yml +0 -0
  89. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/postgresql-restart.yml +0 -0
  90. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/postgresql.yml +0 -0
  91. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/service-logs.yml +0 -0
  92. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/shop-logrotate.yml +0 -0
  93. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/playbooks/shop.yml +0 -0
  94. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/disk-report-20261008170744.diff +0 -0
  95. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/disk-report-20261008170744.json +0 -0
  96. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/journal-vacuum-20261008171016.diff +0 -0
  97. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/journal-vacuum-20261008171016.json +0 -0
  98. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/postgresql-reinit-20261008171010.diff +0 -0
  99. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/postgresql-reinit-20261008171010.json +0 -0
  100. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/postgresql-restart-20261008170624.diff +0 -0
  101. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/postgresql-restart-20261008170624.json +0 -0
  102. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170612.diff +0 -0
  103. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170612.json +0 -0
  104. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170740.diff +0 -0
  105. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170740.json +0 -0
  106. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170742.diff +0 -0
  107. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170742.json +0 -0
  108. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170838.diff +0 -0
  109. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170838.json +0 -0
  110. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170920.diff +0 -0
  111. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/service-logs-20261008170920.json +0 -0
  112. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/shop-logrotate-20261008170752.diff +0 -0
  113. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full/shop-logrotate-20261008170752.json +0 -0
  114. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/disk-report-20261008204348.diff +0 -0
  115. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/disk-report-20261008204348.json +0 -0
  116. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/disk-report-20261008204421.diff +0 -0
  117. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/disk-report-20261008204421.json +0 -0
  118. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/postgresql-restart-20261008204358.diff +0 -0
  119. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/postgresql-restart-20261008204358.json +0 -0
  120. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/shop-logrotate-20261008204351.diff +0 -0
  121. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/recordings/disk-full-approved/shop-logrotate-20261008204351.json +0 -0
  122. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/scenario/break.yml +0 -0
  123. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/incident/site.yml +0 -0
  124. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/pyproject.toml +0 -0
  125. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/src/cloudfall_demo/__init__.py +0 -0
  126. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/src/cloudfall_demo/server.py +0 -0
  127. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/src/cloudfall_demo/static/evaluation.json +0 -0
  128. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/src/cloudfall_demo/static/index.html +0 -0
  129. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/tests/test_demo.py +0 -0
  130. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/demo/uv.lock +0 -0
  131. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/ansible-context.md +0 -0
  132. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/assets/logo.svg +0 -0
  133. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/availability-design.md +0 -0
  134. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/brownfield-design.md +0 -0
  135. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/decisions/2026-09-14-no-ansible-replacement.md +0 -0
  136. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/decisions/2026-09-14-no-nixos-base.md +0 -0
  137. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/decisions/2026-09-14-no-rust-rewrite.md +0 -0
  138. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/decisions.md +0 -0
  139. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/fleet-goals.md +0 -0
  140. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/hybrid-storage-design.md +0 -0
  141. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/images/demo.png +0 -0
  142. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/landscape.md +0 -0
  143. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/logging-service-guide.md +0 -0
  144. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/neon-migration-guide.md +0 -0
  145. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/new-server-storage-guide.md +0 -0
  146. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/operator-guide.md +0 -0
  147. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-debian-container-m1.md +0 -0
  148. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-dns-cutover.md +0 -0
  149. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-guided-data-migration.md +0 -0
  150. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-hetzner-m1.md +0 -0
  151. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-hetzner-m2-m3.md +0 -0
  152. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-hetzner-m4-m5.md +0 -0
  153. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-08-render-cutover.md +0 -0
  154. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-10-hetzner-m7.md +0 -0
  155. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-10-hetzner-m8.md +0 -0
  156. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-10-hetzner-m9.md +0 -0
  157. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-09-11-hetzner-m10.md +0 -0
  158. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/proving-runs/2026-10-08-disk-full-agent.md +0 -0
  159. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/render-migration-guide.md +0 -0
  160. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/docs/secrets-guide.md +0 -0
  161. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/README.md +0 -0
  162. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/ansible.cfg +0 -0
  163. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/backup.yml +0 -0
  164. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/baseline.yml +0 -0
  165. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/bootstrap.yml +0 -0
  166. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/data.yml +0 -0
  167. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/deploy.yml +0 -0
  168. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/domains.yml +0 -0
  169. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/health.yml +0 -0
  170. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/inspect.yml +0 -0
  171. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/logging.yml +0 -0
  172. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/restart.yml +0 -0
  173. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/rollback.yml +0 -0
  174. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/services.yml +0 -0
  175. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/playbooks/time.yml +0 -0
  176. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_access/defaults/main.yml +0 -0
  177. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_access/handlers/main.yml +0 -0
  178. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_access/meta/argument_specs.yml +0 -0
  179. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_access/meta/main.yml +0 -0
  180. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_access/tasks/main.yml +0 -0
  181. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_bootstrap/defaults/main.yml +0 -0
  182. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_bootstrap/meta/argument_specs.yml +0 -0
  183. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_bootstrap/meta/main.yml +0 -0
  184. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_bootstrap/tasks/main.yml +0 -0
  185. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_data_migration/defaults/main.yml +0 -0
  186. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_data_migration/meta/argument_specs.yml +0 -0
  187. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_data_migration/meta/main.yml +0 -0
  188. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_data_migration/tasks/main.yml +0 -0
  189. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/meta/main.yml +0 -0
  190. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/tasks/health.yml +0 -0
  191. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/tasks/health_gate.yml +0 -0
  192. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/tasks/restart.yml +0 -0
  193. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/tasks/rollback.yml +0 -0
  194. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_deploy/templates/component.service.j2 +0 -0
  195. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_firewall/defaults/main.yml +0 -0
  196. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_firewall/handlers/main.yml +0 -0
  197. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_firewall/meta/argument_specs.yml +0 -0
  198. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_firewall/meta/main.yml +0 -0
  199. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_firewall/tasks/main.yml +0 -0
  200. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_firewall/templates/nftables.conf.j2 +0 -0
  201. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_inspect/defaults/main.yml +0 -0
  202. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_inspect/meta/argument_specs.yml +0 -0
  203. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_inspect/meta/main.yml +0 -0
  204. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_inspect/tasks/main.yml +0 -0
  205. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/defaults/main.yml +0 -0
  206. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/handlers/main.yml +0 -0
  207. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/meta/argument_specs.yml +0 -0
  208. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/meta/main.yml +0 -0
  209. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/tasks/main.yml +0 -0
  210. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager-defaults.j2 +0 -0
  211. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager.yaml.j2 +0 -0
  212. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/grafana.ini.j2 +0 -0
  213. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/loki-datasource.yml.j2 +0 -0
  214. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/loki-systemd-override.conf.j2 +0 -0
  215. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/loki.yaml.j2 +0 -0
  216. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/nginx-loki-gateway.conf.j2 +0 -0
  217. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-datasource.yml.j2 +0 -0
  218. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-defaults.j2 +0 -0
  219. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-rules.yaml.j2 +0 -0
  220. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus.yaml.j2 +0 -0
  221. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/defaults/main.yml +0 -0
  222. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/handlers/main.yml +0 -0
  223. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/meta/argument_specs.yml +0 -0
  224. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/meta/main.yml +0 -0
  225. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/tasks/main.yml +0 -0
  226. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/templates/alloy-systemd-override.conf.j2 +0 -0
  227. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_logging_collector/templates/config.alloy.j2 +0 -0
  228. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/defaults/main.yml +0 -0
  229. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/handlers/main.yml +0 -0
  230. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/meta/argument_specs.yml +0 -0
  231. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/meta/main.yml +0 -0
  232. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/tasks/apply_virtual_host.yml +0 -0
  233. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/tasks/main.yml +0 -0
  234. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_nginx_site/templates/domain-site.conf.j2 +0 -0
  235. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/defaults/main.yml +0 -0
  236. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/handlers/main.yml +0 -0
  237. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/meta/argument_specs.yml +0 -0
  238. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/meta/main.yml +0 -0
  239. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/tasks/main.yml +0 -0
  240. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/backup.service.j2 +0 -0
  241. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/backup.sh.j2 +0 -0
  242. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/backup.timer.j2 +0 -0
  243. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/cloudfall.conf.j2 +0 -0
  244. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.service.j2 +0 -0
  245. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.sh.j2 +0 -0
  246. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.timer.j2 +0 -0
  247. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/defaults/main.yml +0 -0
  248. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/handlers/main.yml +0 -0
  249. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/meta/argument_specs.yml +0 -0
  250. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/meta/main.yml +0 -0
  251. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/tasks/main.yml +0 -0
  252. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/backup.service.j2 +0 -0
  253. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/backup.sh.j2 +0 -0
  254. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/backup.timer.j2 +0 -0
  255. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/cloudfall.conf.j2 +0 -0
  256. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/restore-check.service.j2 +0 -0
  257. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/restore-check.sh.j2 +0 -0
  258. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_redis/templates/restore-check.timer.j2 +0 -0
  259. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_time/defaults/main.yml +0 -0
  260. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_time/meta/argument_specs.yml +0 -0
  261. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_time/meta/main.yml +0 -0
  262. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_time/tasks/main.yml +0 -0
  263. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_unattended_upgrades/defaults/main.yml +0 -0
  264. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_unattended_upgrades/meta/argument_specs.yml +0 -0
  265. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_unattended_upgrades/meta/main.yml +0 -0
  266. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/ansible/roles/cloudfall_unattended_upgrades/tasks/main.yml +0 -0
  267. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/__init__.py +0 -0
  268. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/__main__.py +0 -0
  269. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/ansible_inventory.py +0 -0
  270. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/artifact.py +0 -0
  271. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/cli.py +0 -0
  272. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/playbook.py +0 -0
  273. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/src/cloudfall_engine/py.typed +0 -0
  274. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_ansible_inventory.py +0 -0
  275. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_artifact_builder.py +0 -0
  276. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_deploy_templates.py +0 -0
  277. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_firewall_templates.py +0 -0
  278. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_logging_templates.py +0 -0
  279. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_nginx_site_role.py +0 -0
  280. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_nginx_site_templates.py +0 -0
  281. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_playbook.py +0 -0
  282. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_postgresql_templates.py +0 -0
  283. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/engine/tests/test_time_role.py +0 -0
  284. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/.gitignore +0 -0
  285. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/README.md +0 -0
  286. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/ansible.cfg +0 -0
  287. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/inventories/production/hosts.yml +0 -0
  288. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/converge.yml +0 -0
  289. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/disk-report.yml +0 -0
  290. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/journal-vacuum.yml +0 -0
  291. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/postgresql-reinit.yml +0 -0
  292. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/postgresql-restart.yml +0 -0
  293. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/service-logs.yml +0 -0
  294. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/operations/shop-logrotate.yml +0 -0
  295. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/disk-report.yml +0 -0
  296. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/journal-vacuum.yml +0 -0
  297. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/postgresql-reinit.yml +0 -0
  298. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/postgresql-restart.yml +0 -0
  299. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/postgresql.yml +0 -0
  300. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/service-logs.yml +0 -0
  301. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/shop-logrotate.yml +0 -0
  302. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/playbooks/shop.yml +0 -0
  303. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/disk-report-20261008170744.diff +0 -0
  304. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/disk-report-20261008170744.json +0 -0
  305. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/journal-vacuum-20261008171016.diff +0 -0
  306. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/journal-vacuum-20261008171016.json +0 -0
  307. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/postgresql-reinit-20261008171010.diff +0 -0
  308. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/postgresql-reinit-20261008171010.json +0 -0
  309. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/postgresql-restart-20261008170624.diff +0 -0
  310. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/postgresql-restart-20261008170624.json +0 -0
  311. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170612.diff +0 -0
  312. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170612.json +0 -0
  313. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170740.diff +0 -0
  314. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170740.json +0 -0
  315. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170742.diff +0 -0
  316. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170742.json +0 -0
  317. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170838.diff +0 -0
  318. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170838.json +0 -0
  319. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170920.diff +0 -0
  320. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/service-logs-20261008170920.json +0 -0
  321. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/shop-logrotate-20261008170752.diff +0 -0
  322. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full/shop-logrotate-20261008170752.json +0 -0
  323. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/disk-report-20261008204348.diff +0 -0
  324. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/disk-report-20261008204348.json +0 -0
  325. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/disk-report-20261008204421.diff +0 -0
  326. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/disk-report-20261008204421.json +0 -0
  327. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/postgresql-restart-20261008204358.diff +0 -0
  328. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/postgresql-restart-20261008204358.json +0 -0
  329. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/shop-logrotate-20261008204351.diff +0 -0
  330. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/recordings/disk-full-approved/shop-logrotate-20261008204351.json +0 -0
  331. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/scenario/break.yml +0 -0
  332. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/disk-full-incident/site.yml +0 -0
  333. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/render-demo/README.md +0 -0
  334. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/render-demo/pyproject.toml +0 -0
  335. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/render-demo/render.yaml +0 -0
  336. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/render-demo/src/demo/__init__.py +0 -0
  337. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/render-demo/src/demo/asgi.py +0 -0
  338. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/examples/render-demo/uv.lock +0 -0
  339. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/README.md +0 -0
  340. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/__init__.py +0 -0
  341. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/__main__.py +0 -0
  342. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/agent_server.py +0 -0
  343. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/agent_tools.py +0 -0
  344. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/ansible_api.py +0 -0
  345. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/ansible_reader.py +0 -0
  346. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/audit.py +0 -0
  347. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/authoring.py +0 -0
  348. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/callback_plugins/__init__.py +0 -0
  349. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/callback_plugins/cloudfall_host_stats.py +0 -0
  350. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/catalog.py +0 -0
  351. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/cli.py +0 -0
  352. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/commands.py +0 -0
  353. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/cutover.py +0 -0
  354. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/dashboard.py +0 -0
  355. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/dashboard_server.py +0 -0
  356. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/domain.py +0 -0
  357. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/importer.py +0 -0
  358. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/inventory.py +0 -0
  359. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/investigation.py +0 -0
  360. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/lifecycle.py +0 -0
  361. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/migrate.py +0 -0
  362. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/observation.py +0 -0
  363. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/observe.py +0 -0
  364. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/operations.py +0 -0
  365. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/operator.py +0 -0
  366. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/project.py +0 -0
  367. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/py.typed +0 -0
  368. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/recording.py +0 -0
  369. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/render_api.py +0 -0
  370. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/resources.py +0 -0
  371. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/secrets.py +0 -0
  372. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/service_evidence.py +0 -0
  373. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/src/cloudfall/validation.py +0 -0
  374. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_agent_server.py +0 -0
  375. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_ansible_reader.py +0 -0
  376. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_app.py +0 -0
  377. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_arguments.py +0 -0
  378. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_audit.py +0 -0
  379. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_authoring.py +0 -0
  380. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_catalog.py +0 -0
  381. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_commands.py +0 -0
  382. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_cutover.py +0 -0
  383. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_dashboard_server.py +0 -0
  384. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_data_migration.py +0 -0
  385. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_decision.py +0 -0
  386. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_host_stats.py +0 -0
  387. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_importer.py +0 -0
  388. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_inventory.py +0 -0
  389. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_investigation.py +0 -0
  390. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_lifecycle.py +0 -0
  391. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_migrate.py +0 -0
  392. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_observe.py +0 -0
  393. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_operations_dashboard.py +0 -0
  394. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_operator.py +0 -0
  395. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_project.py +0 -0
  396. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_render_api.py +0 -0
  397. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_resources.py +0 -0
  398. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_secrets.py +0 -0
  399. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_service_evidence.py +0 -0
  400. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_validation.py +0 -0
  401. {cloudfall-0.7.0rc5 → cloudfall-0.8.0rc2}/sdk/tests/test_why.py +0 -0
@@ -6,6 +6,39 @@ Notable changes to Cloudfall. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.7.0] - 2026-10-19
10
+
11
+ ### Fixed
12
+
13
+ - A decision records a host `changed` when any task of the play changed it,
14
+ not only the last one. The check preview, the execution and the verify run
15
+ read each host's counts from the play recap through a callback the SDK
16
+ ships (`cloudfall_host_stats`), in place of Ansible's `tree` callback,
17
+ which keeps only the last task's result: a run whose recap said
18
+ `changed=2` was recorded `changed: []`, and a verify run that changed a
19
+ host was recorded `verified`. The callback is added ahead of the callback
20
+ directories the team's `ansible.cfg` names, not in place of them (#76)
21
+ - The Neon guide says each database belongs to an application Cloudfall
22
+ deploys on the same server, and no longer suggests an app on another host
23
+ can reach it; the brownfield design's operation example uses a target
24
+ scope the schema accepts (`host`); the site no longer offers a PostgreSQL
25
+ on its own, and its navigation and footer links are easier to tap on a
26
+ phone (#81)
27
+
28
+ ### Added
29
+
30
+ - `cloudfall why` tells which agent turn asked for a decision. Each step of
31
+ an investigation record now keeps the `turn` that made its call (optional
32
+ in `agent-investigation.schema.json`; older records have none). `why`
33
+ reads the investigation records (`--investigations`, default
34
+ `investigations`, served to MCP clients from the same place) and adds to a
35
+ decision's story the model, the endpoint, the alert, the turn, and that
36
+ turn's reasoning with the endpoint's response and request ids; the answer
37
+ carries the same as `agent`. A decision a later call only repeated keeps
38
+ the turn that asked first. An investigation record that is not valid JSON
39
+ or does not match its schema stops `why` with `RECORD_INVALID`
40
+ (`why_investigation_invalid`) (#78)
41
+
9
42
  ## [0.6.0] - 2026-10-10
10
43
 
11
44
  ### Added
@@ -828,7 +861,8 @@ on disposable Hetzner Cloud Debian 13 servers (see
828
861
  exposed freely, every server-changing tool gated behind a two-step
829
862
  confirmation handshake
830
863
 
831
- [Unreleased]: https://github.com/cloudfall-dev/cloudfall/compare/v0.6.0...HEAD
864
+ [Unreleased]: https://github.com/cloudfall-dev/cloudfall/compare/v0.7.0...HEAD
865
+ [0.7.0]: https://github.com/cloudfall-dev/cloudfall/compare/v0.6.0...v0.7.0
832
866
  [0.6.0]: https://github.com/cloudfall-dev/cloudfall/compare/v0.5.1...v0.6.0
833
867
  [0.5.1]: https://github.com/cloudfall-dev/cloudfall/compare/v0.5.0...v0.5.1
834
868
  [0.5.0]: https://github.com/cloudfall-dev/cloudfall/compare/v0.4.0...v0.5.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cloudfall
3
- Version: 0.7.0rc5
3
+ Version: 0.8.0rc2
4
4
  Summary: The operator's record for self-hosted servers run by an AI agent: Ansible executes, Cloudfall gates, verifies and keeps the receipts
5
5
  Project-URL: Homepage, https://cloudfall.dev
6
6
  Project-URL: Repository, https://github.com/cloudfall-dev/cloudfall
@@ -266,6 +266,7 @@ per-table row counts and writes a receipt. See the
266
266
  | [Operator](docs/operator-guide.md) | The always-on operator, proposals, approvals, autonomy policy |
267
267
  | [Disk-full incident](examples/disk-full-incident) | `agent investigate` with Nemotron on a real broken host, live or replayed |
268
268
  | [Secrets](docs/secrets-guide.md) | sops/age secrets rendered into per-component env files |
269
+ | [CI deploy](docs/ci-deploy-guide.md) | Deploying from GitHub Actions with per-environment keys and required reviewers |
269
270
  | [Logging](docs/logging-service-guide.md) | Loki, Grafana and Alloy behind an mTLS gateway |
270
271
  | [Storage](docs/new-server-storage-guide.md) | Destructive, new-server-only RAID provisioning ([design](docs/hybrid-storage-design.md)) |
271
272
  | [Architecture](ARCHITECTURE.md) | Module boundaries and the long-term fleet vision |
@@ -238,6 +238,7 @@ per-table row counts and writes a receipt. See the
238
238
  | [Operator](docs/operator-guide.md) | The always-on operator, proposals, approvals, autonomy policy |
239
239
  | [Disk-full incident](examples/disk-full-incident) | `agent investigate` with Nemotron on a real broken host, live or replayed |
240
240
  | [Secrets](docs/secrets-guide.md) | sops/age secrets rendered into per-component env files |
241
+ | [CI deploy](docs/ci-deploy-guide.md) | Deploying from GitHub Actions with per-environment keys and required reviewers |
241
242
  | [Logging](docs/logging-service-guide.md) | Loki, Grafana and Alloy behind an mTLS gateway |
242
243
  | [Storage](docs/new-server-storage-guide.md) | Destructive, new-server-only RAID provisioning ([design](docs/hybrid-storage-design.md)) |
243
244
  | [Architecture](ARCHITECTURE.md) | Module boundaries and the long-term fleet vision |
@@ -0,0 +1,3 @@
1
+ ### Added
2
+
3
+ - A CI deploy guide (`docs/ci-deploy-guide.md`): one GitHub Actions workflow that renders secrets, builds the artifact with `cloudfall-engine artifact build`, deploys with `cloudfall deploy --yes` and uploads the receipts, with one SSH key and one age key per environment and required reviewers standing in for `approval: manual`, which `deploy` does not enforce; not yet run against a real server (#89)
@@ -0,0 +1,3 @@
1
+ ### Fixed
2
+
3
+ - A deploy of a component with `healthCheck: {type: none}` no longer fails its health gate on the missing HTTP fields and stops the release: the gate now requires the systemd unit to be active right after the restart and still active, with no automatic restart in between, 10 seconds later, so a worker that crashes or crash-loops on start still fails and rolls back (#96)
@@ -0,0 +1,3 @@
1
+ ### Added
2
+
3
+ - An open proposal whose basis hosts have been observed again since is now reported as stale: `decisions list`, `decisions show` and `cloudfall why` add `stale` and `staleHosts` to each `proposed` decision, `--stale` on `decisions list` and `why` keeps only those, and `decisions approve` names each stale host and its newer observation time before asking for the id. Worked out on read from the observation files; the record and its schema do not change (#26)
@@ -0,0 +1,385 @@
1
+ # CI deploy guide
2
+
3
+ Every other guide runs Cloudfall from an operator's machine: your age key in
4
+ `~/.config/cloudfall/age.key`, your SSH agent, your shell. This guide moves
5
+ the same deploy into a GitHub Actions workflow, so a merge to `main` updates
6
+ staging and a production deploy waits for a person's approval.
7
+
8
+ The record does not change. The runner renders the secrets, builds the
9
+ artifact and deploys it with the same commands you run by hand, and the
10
+ receipts they write are uploaded with the run so they outlive the runner.
11
+
12
+ > **Status:** not proven live. The workflow below is written from the code
13
+ > and checked against the flags of Cloudfall 0.7.0 (`--help` and the
14
+ > source), but it has not had a run against a real server yet. Treat the
15
+ > first run on staging as the proving run, and open an issue for anything
16
+ > that differs.
17
+
18
+ ## What the runner needs
19
+
20
+ The runner is a controller like your laptop. It needs:
21
+
22
+ - The project checked out: the repository `cloudfall init` created, with its
23
+ `pyproject.toml` and `uv.lock` committed
24
+ - `uv`, which installs Python 3.14, Cloudfall and `ansible-core` from the
25
+ project's lock file
26
+ - `ansible-playbook` 2.21.0 or later: it comes with Cloudfall
27
+ (`ansible-core>=2.21,<2.22`), so `uv run` finds it in the project's
28
+ virtualenv
29
+ - `git` 2.24.0 or later, which `artifact build` uses to clone the
30
+ component's repository; GitHub's Ubuntu runners ship a newer one
31
+ - `sops` 3.8.0 or later, which `secrets render` runs to decrypt fragments;
32
+ `age` itself is not needed, because sops decrypts age files on its own
33
+ - An SSH key the servers accept, and their host keys in `known_hosts`
34
+ - The age key that decrypts this environment's secrets
35
+
36
+ These are the versions the commands check before they run (their
37
+ `required_tools`); a missing or older tool stops the command with an error
38
+ naming it. `uv run cloudfall doctor` checks all of them at once, so the
39
+ workflow runs it first.
40
+
41
+ ## One key per environment
42
+
43
+ A runner should hold the keys of one environment and nothing else. Cloudfall
44
+ already scopes two of them.
45
+
46
+ **SSH keys.** Each `SshPublicKey` names an environment, and the baseline
47
+ installs a server's active keys only when their environment matches the
48
+ server's. Make a key for the staging runner and declare it for staging:
49
+
50
+ ```sh
51
+ ssh-keygen -t ed25519 -N "" -C "ci staging" -f ci-staging
52
+ uv run cloudfall add ssh-key ci-staging.pub --owner ci --id ci-staging --environment staging
53
+ uv run cloudfall-engine inventory render --output tmp/ansible-inventory.json
54
+ uv run cloudfall-engine playbook run baseline --inventory tmp/ansible-inventory.json
55
+ ```
56
+
57
+ The baseline writes the key into root's `authorized_keys` on staging servers
58
+ only, so the staging runner cannot log in to production. A server whose
59
+ `ssh.user` is not `root` needs the key installed for that user by hand.
60
+
61
+ **Age keys.** This one needs a change to your project. `cloudfall init`
62
+ writes a `.sops.yaml` with one rule that encrypts every fragment under
63
+ `secrets/` to one key. A runner holding that key can decrypt everything.
64
+ Split the rule by environment directory, and list your own key in each rule
65
+ so you can still edit every file:
66
+
67
+ ```sh
68
+ age-keygen -o staging.key
69
+ age-keygen -o production.key
70
+ ```
71
+
72
+ ```yaml
73
+ creation_rules:
74
+ - path_regex: secrets/staging/.*\.env$
75
+ age: age1staging...,age1yourown...
76
+ - path_regex: secrets/production/.*\.env$
77
+ age: age1production...,age1yourown...
78
+ ```
79
+
80
+ sops uses the first rule that matches a path. Files encrypted before the
81
+ change keep their old recipients until you re-encrypt them:
82
+
83
+ ```sh
84
+ sops updatekeys secrets/staging/crm.env
85
+ ```
86
+
87
+ Why this stops a staging runner: a secret reference
88
+ `{environment: production, path: /crm}` resolves from
89
+ `secrets/production/crm.env`, and `secrets render` decrypts only the files
90
+ the component's references name. If a component on the staging runner
91
+ references a production fragment, sops has no matching key, and the render
92
+ fails with `secrets_decrypt_failed` before anything is written. Cloudfall
93
+ does not itself check that a component's references name the environment of
94
+ its servers; the key the runner holds is what enforces it.
95
+
96
+ Store `staging.key` and `production.key` in a password manager, then delete
97
+ the local copies once they are in GitHub.
98
+
99
+ ## GitHub environments and secrets
100
+
101
+ Create one GitHub environment per Cloudfall environment, with the same name
102
+ (`staging`, `production`), and put three secrets in each:
103
+
104
+ | Secret | Content |
105
+ | --- | --- |
106
+ | `CLOUDFALL_SSH_KEY` | The private key of that environment's runner key, such as `ci-staging` |
107
+ | `CLOUDFALL_KNOWN_HOSTS` | The `known_hosts` lines of that environment's servers |
108
+ | `SOPS_AGE_KEY` | The age key file of that environment, such as `staging.key` |
109
+
110
+ ```sh
111
+ gh secret set CLOUDFALL_SSH_KEY --env staging < ci-staging
112
+ gh secret set SOPS_AGE_KEY --env staging < staging.key
113
+ ssh-keygen -F 203.0.113.20 > staging-known-hosts
114
+ gh secret set CLOUDFALL_KNOWN_HOSTS --env staging < staging-known-hosts
115
+ ```
116
+
117
+ `ssh-keygen -F` reads the host key your own machine already trusts, which is
118
+ safer than scanning it from the runner. For a server on another port, look it
119
+ up as `[203.0.113.20]:2222`.
120
+
121
+ In the repository's settings, under Environments:
122
+
123
+ - `production`: add required reviewers and turn on "Prevent self-review"
124
+ - Both: limit deployment branches to `main`, so a job on another branch never
125
+ sees the secrets
126
+
127
+ ## The workflow
128
+
129
+ Save this as `.github/workflows/deploy.yml` in the project repository.
130
+ Replace `crm-backend-staging` with your staging component.
131
+
132
+ ```yaml
133
+ name: deploy
134
+
135
+ on:
136
+ push:
137
+ branches: [main]
138
+ workflow_dispatch:
139
+ inputs:
140
+ environment:
141
+ description: Cloudfall environment, and the GitHub environment of the same name
142
+ type: choice
143
+ options: [staging, production]
144
+ default: staging
145
+ component:
146
+ description: Component to deploy
147
+ required: true
148
+ default: crm-backend-staging
149
+ ref:
150
+ description: Git ref of the component's repository to build
151
+ required: true
152
+ default: main
153
+ repository_dispatch:
154
+ types: [deploy]
155
+
156
+ concurrency:
157
+ group: deploy-${{ inputs.environment || github.event.client_payload.environment || 'staging' }}
158
+ cancel-in-progress: false
159
+
160
+ permissions:
161
+ contents: read
162
+
163
+ jobs:
164
+ deploy:
165
+ runs-on: ubuntu-24.04
166
+ environment: ${{ inputs.environment || github.event.client_payload.environment || 'staging' }}
167
+ env:
168
+ COMPONENT: ${{ inputs.component || github.event.client_payload.component || 'crm-backend-staging' }}
169
+ REF: ${{ inputs.ref || github.event.client_payload.ref || 'main' }}
170
+ SOPS_VERSION: v3.13.3
171
+ steps:
172
+ - uses: actions/checkout@v7
173
+
174
+ - uses: astral-sh/setup-uv@v10.1.0
175
+
176
+ - name: Install Cloudfall from the lock file
177
+ run: uv sync --locked
178
+
179
+ - name: Install sops
180
+ run: |
181
+ base="https://github.com/getsops/sops/releases/download/$SOPS_VERSION"
182
+ curl -fsSLO "$base/sops-$SOPS_VERSION.linux.amd64"
183
+ curl -fsSLO "$base/sops-$SOPS_VERSION.checksums.txt"
184
+ sha256sum --check --ignore-missing "sops-$SOPS_VERSION.checksums.txt"
185
+ sudo install -m 0755 "sops-$SOPS_VERSION.linux.amd64" /usr/local/bin/sops
186
+
187
+ - name: Load the SSH key
188
+ env:
189
+ CLOUDFALL_SSH_KEY: ${{ secrets.CLOUDFALL_SSH_KEY }}
190
+ CLOUDFALL_KNOWN_HOSTS: ${{ secrets.CLOUDFALL_KNOWN_HOSTS }}
191
+ run: |
192
+ install -m 0700 -d ~/.ssh
193
+ printf '%s\n' "$CLOUDFALL_KNOWN_HOSTS" > ~/.ssh/known_hosts
194
+ eval "$(ssh-agent -s)"
195
+ printf '%s\n' "$CLOUDFALL_SSH_KEY" | ssh-add -
196
+ echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
197
+
198
+ - name: Write the age key
199
+ env:
200
+ SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}
201
+ run: |
202
+ install -m 0600 /dev/null "$RUNNER_TEMP/age.key"
203
+ printf '%s\n' "$SOPS_AGE_KEY" > "$RUNNER_TEMP/age.key"
204
+ echo "SOPS_AGE_KEY_FILE=$RUNNER_TEMP/age.key" >> "$GITHUB_ENV"
205
+
206
+ - name: Check the toolchain
207
+ run: uv run cloudfall doctor
208
+
209
+ - name: Validate the config
210
+ run: uv run cloudfall config validate
211
+
212
+ - name: Render secrets
213
+ run: uv run cloudfall secrets render "$COMPONENT"
214
+
215
+ - name: Build the artifact
216
+ id: build
217
+ run: |
218
+ uv run cloudfall-engine artifact build "$COMPONENT" --ref "$REF" --format json > "$RUNNER_TEMP/build.json"
219
+ echo "release=$(jq -r '.data.release' "$RUNNER_TEMP/build.json")" >> "$GITHUB_OUTPUT"
220
+
221
+ - name: Show the plan
222
+ env:
223
+ RELEASE: ${{ steps.build.outputs.release }}
224
+ run: uv run cloudfall deploy "$COMPONENT" --release "$RELEASE"
225
+
226
+ - name: Deploy
227
+ env:
228
+ RELEASE: ${{ steps.build.outputs.release }}
229
+ run: uv run cloudfall deploy "$COMPONENT" --release "$RELEASE" --env-file "tmp/env/$COMPONENT.env" --yes
230
+
231
+ - name: Remove the decrypted files
232
+ if: always()
233
+ run: rm -f "tmp/env/$COMPONENT.env" "$RUNNER_TEMP/age.key"
234
+
235
+ - name: Upload the receipts
236
+ if: always()
237
+ uses: actions/upload-artifact@v7
238
+ with:
239
+ name: cloudfall-${{ env.COMPONENT }}-${{ github.run_id }}
240
+ path: |
241
+ tmp/artifacts/**/*.json
242
+ tmp/releases/
243
+ tmp/env-receipts/
244
+ retention-days: 90
245
+ ```
246
+
247
+ What each step does:
248
+
249
+ - **Install Cloudfall from the lock file.** `uv sync --locked` installs the
250
+ Cloudfall release the project pins, and fails if `uv.lock` is out of date
251
+ rather than resolving a different one
252
+ - **Install sops.** The binary is checked against the release's checksum
253
+ file before it is installed
254
+ - **Load the SSH key.** The key goes into an `ssh-agent`, never onto disk;
255
+ Ansible reaches the agent through `SSH_AUTH_SOCK`
256
+ - **Write the age key.** The file is created `0600` before the key is
257
+ written into it, and `SOPS_AGE_KEY_FILE` points sops at it, as on your own
258
+ machine
259
+ - **Render secrets.** Writes `tmp/env/<component>.env` (`0600`) and the
260
+ receipt `tmp/env-receipts/<component>.json`; the log shows key names and a
261
+ hash, never a value
262
+ - **Build the artifact.** Clones the component's repository at `REF` and
263
+ packages it under `tmp/artifacts/<component>/`
264
+ - **Show the plan.** `deploy` without `--yes` validates the release and
265
+ prints which servers it would change, and changes nothing
266
+ - **Deploy.** Ships the release behind the component's health check, and
267
+ writes the release receipt `tmp/releases/<component>.json`
268
+ - **Remove the decrypted files.** GitHub-hosted runners are thrown away
269
+ after the job; on a self-hosted runner this step is what keeps the values
270
+ off its disk
271
+
272
+ If the project is not at the root of the repository, set `CLOUDFALL_PROJECT`
273
+ to its directory in the job's `env`.
274
+
275
+ ## Passing the release id
276
+
277
+ `cloudfall deploy` takes the release to ship as `--release`, and only
278
+ `artifact build` produces one. Two things trip people up:
279
+
280
+ - `artifact build` is a `cloudfall-engine` command, not a `cloudfall` one.
281
+ Through an agent it is the `build_artifact` tool of `cloudfall mcp serve`
282
+ - The release id is in the build's JSON answer at `data.release`, such as
283
+ `20260101T000000Z-abcdef0` (the build time and the short commit). The
284
+ workflow reads it with `jq` and passes it on as a step output
285
+
286
+ Off a terminal both CLIs answer in JSON by default; the workflow passes
287
+ `--format json` so the build step does not depend on that.
288
+
289
+ The artifact is a file on the runner. `deploy` verifies it from
290
+ `tmp/artifacts` before shipping it, so build and deploy must run in the same
291
+ job. Splitting them across jobs means uploading `tmp/artifacts/` from one and
292
+ downloading it into the other.
293
+
294
+ `REF` is a ref of the component's repository (its `repository.url`), not of
295
+ the project repository. A push to the project's `main` therefore rebuilds the
296
+ component's `main`. To ship to production exactly the commit staging ran,
297
+ dispatch the production deploy with that commit's full hash as `ref`; the
298
+ staging receipt records it as `gitCommit`. The build packages the same
299
+ commit again, which gives a new release id and archive.
300
+
301
+ ## Triggering it from an application repository
302
+
303
+ An application repository can start a deploy when its own `main` changes,
304
+ with a `repository_dispatch` event on the project repository:
305
+
306
+ ```sh
307
+ gh api repos/acme/fleet/dispatches \
308
+ -f event_type=deploy \
309
+ -f 'client_payload[component]=crm-backend-staging' \
310
+ -f 'client_payload[environment]=staging' \
311
+ -f "client_payload[ref]=$GITHUB_SHA"
312
+ ```
313
+
314
+ Run it as a step in the application's workflow, authenticated with a token
315
+ that may write the project repository's contents (a fine-grained token or a
316
+ GitHub App token, stored as a secret of the application repository). The
317
+ event runs the workflow from the project's default branch, and a dispatch
318
+ naming `production` still waits for its reviewers.
319
+
320
+ ## Approval: who presses the button
321
+
322
+ Every `Application` declares `spec.approval: manual` or `automatic`.
323
+ Cloudfall validates the field and writes it into the rendered inventory, but
324
+ no command acts on it today: `cloudfall deploy --yes` deploys an application
325
+ marked `manual` exactly as it deploys one marked `automatic`. The only gate
326
+ inside `deploy` is `--yes`, and a workflow that passes `--yes` has approved
327
+ its own deploy.
328
+
329
+ So in CI, the approval comes from GitHub, not from Cloudfall:
330
+
331
+ - Give every environment that holds an application marked `manual` required
332
+ reviewers, and turn on "Prevent self-review", so the person who pushed the
333
+ change cannot approve its deploy
334
+ - The job waits before its first step until a reviewer approves, so it never
335
+ sees the SSH key or the age key of that environment without approval
336
+ - Leave `staging` without reviewers if its applications are `automatic`
337
+
338
+ Read `approval: manual` as your declaration that such a gate must exist; the
339
+ environment's reviewers are what enforce it. The approval itself lands in
340
+ GitHub's deployment history, not in Cloudfall's record.
341
+
342
+ ## Keeping the receipts
343
+
344
+ `tmp/` is ignored by git, and the runner disappears with the job. The upload
345
+ step keeps, for 90 days:
346
+
347
+ - `tmp/artifacts/<component>/<release>.json`: the artifact metadata, with the
348
+ commit and the archive's SHA-256
349
+ - `tmp/releases/<component>.json`: the release receipt from `deploy`
350
+ - `tmp/env-receipts/<component>.json`: the key names and hash of the
351
+ rendered environment file, never the values
352
+
353
+ It never uploads `tmp/env/`, which holds the decrypted values. Add
354
+ `tmp/artifacts/` whole if you want the archives kept as well.
355
+
356
+ If you want the receipts in git, copy them to a tracked directory and commit
357
+ them back from the job. Give the job `contents: write` and add a step after
358
+ the deploy:
359
+
360
+ ```yaml
361
+ - name: Commit the receipt
362
+ env:
363
+ RELEASE: ${{ steps.build.outputs.release }}
364
+ run: |
365
+ mkdir -p receipts/releases
366
+ cp "tmp/releases/$COMPONENT.json" "receipts/releases/$COMPONENT-$RELEASE.json"
367
+ git config user.name "github-actions[bot]"
368
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
369
+ git add receipts/releases
370
+ git commit -m "Record $COMPONENT $RELEASE"
371
+ git push
372
+ ```
373
+
374
+ Only the kind directories are read as resources, so `receipts/` is safe in
375
+ the project. A push made with the workflow's own token does not start
376
+ another workflow run, so this does not loop. A branch protection rule on
377
+ `main` will refuse the push unless it allows the workflow to bypass it.
378
+
379
+ ## What this guide does not cover
380
+
381
+ - Moving `artifact build` into the `cloudfall` CLI, so CI installs and pins
382
+ one tool
383
+ - A published reusable action
384
+ - Converging the baseline, services or domains from CI; the same pattern
385
+ applies, with the matching `cloudfall-engine playbook run` command
@@ -71,6 +71,9 @@ shows them); project playbooks by path, with `--roles` directories searched
71
71
  before the bundled roles. To move a project to a newer Cloudfall commit, bump
72
72
  `rev` and run `uv sync`.
73
73
 
74
+ To deploy from a pipeline instead of your own machine, so a merge to `main`
75
+ updates staging, see the [CI deploy guide](ci-deploy-guide.md).
76
+
74
77
  ## The agent contract
75
78
 
76
79
  Humans and AI agents both edit the resources and both run the CLI, with a
@@ -1,6 +1,6 @@
1
1
  # S-001: Stale proposals
2
2
 
3
- status: approved
3
+ status: built
4
4
 
5
5
  ## Problem
6
6
 
@@ -66,4 +66,16 @@ them.
66
66
 
67
67
  ## Issues
68
68
 
69
+ - cloudfall-dev/cloudfall#97: S-001-1, S-001-2, S-001-3, S-001-4, S-001-5, S-001-6, S-001-7, S-001-8, S-001-9
70
+
69
71
  ## Verification
72
+
73
+ - S-001-1: proposed `deploy` on a tmp project with `hz1` observed at 09:00, rewrote `hz1.json` to 09:30, ran `cloudfall decisions list`; and `test_s001_1_a_newer_observation_makes_the_proposal_stale`, the proposal read `"stale": true, "staleHosts": ["hz1"]`
74
+ - S-001-2: `cloudfall decisions list` on the same project before the rewrite, plus `test_s001_2_the_same_missing_or_no_observation_is_not_stale`, `test_s001_2_a_basis_that_names_no_host_is_never_stale` and `test_s001_2_times_compare_as_moments_not_as_text`, each read `"stale": false` with no `staleHosts`
75
+ - S-001-3: `decisions list`, `decisions show` and `cloudfall why` on the tmp project's `ran` facts record, plus `test_s001_3_a_decision_that_is_not_proposed_carries_no_staleness` over verified, ran and superseded records, carried neither key
76
+ - S-001-4: `cloudfall decisions list --stale` and `--stale --status proposed` on the tmp project, plus `test_s001_4_the_stale_filter_keeps_only_the_stale_and_combines_with_status`, returned only the stale proposal, the same set both ways
77
+ - S-001-5: `cloudfall why --stale` on the tmp project, plus `test_s001_5_why_stale_keeps_only_the_stale_and_reports_them`, returned only the stale proposal with `stale` and `staleHosts` identical to `decisions list`
78
+ - S-001-6: `cloudfall decisions show deploy-20261020094012` on the tmp project, plus `test_s001_6_show_reports_a_stale_proposal`, reported `"stale": true, "staleHosts": ["hz1"]`
79
+ - S-001-7: `git hash-object` of both tmp records before and after `decisions list`, `decisions show` and `why`, plus `test_s001_7_working_out_staleness_writes_nothing` (byte and mtime compare), the bytes were unchanged
80
+ - S-001-8: `test_s001_8_approve_names_the_stale_hosts_and_still_runs` runs `decisions approve` at a simulated terminal against a real `ansible-playbook`; the prompt named `web-1 was observed again at 2026-09-21T16:02:10Z` and the decision ended `verified` (no terminal was available to run it by hand)
81
+ - S-001-9: wrote invalid JSON to the tmp project's `hz1.json` and ran `cloudfall decisions list`, plus `test_s001_9_an_observation_file_that_is_not_json_is_record_invalid`, it exited `RECORD_INVALID` (86) naming `tmp/s001/tmp/observed/hz1.json`
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  cloudfall_deploy_environment_file: ""
3
3
  cloudfall_deploy_receipt_directory: ""
4
+ # A component without an HTTP health check passes its gate when its unit is
5
+ # active right after the restart and still active this many seconds later.
6
+ cloudfall_deploy_unit_settle_seconds: 10
4
7
  cloudfall_deploy_uv_version: "0.12.10"
5
8
  cloudfall_deploy_uv_sha256: >-
6
9
  173d95a0c32d18c896c46ba6fafbf3cf9c14ab74b033f81b76c883ef492a976b
@@ -95,6 +95,9 @@ argument_specs:
95
95
  cloudfall_deploy_receipt_directory:
96
96
  type: str
97
97
  default: ""
98
+ cloudfall_deploy_unit_settle_seconds:
99
+ type: int
100
+ default: 10
98
101
  cloudfall_deploy_uv_version:
99
102
  type: str
100
103
  default: "0.12.10"
@@ -203,7 +203,86 @@
203
203
  until: >-
204
204
  cloudfall_deploy_health.status
205
205
  in cloudfall_deploy_component.healthCheck.expectedStatuses
206
- when: not ansible_check_mode
206
+ when:
207
+ - not ansible_check_mode
208
+ - cloudfall_deploy_component.healthCheck.type == "http"
209
+
210
+ - name: Gate the release on an active service unit
211
+ ansible.builtin.command:
212
+ argv:
213
+ - /usr/bin/systemctl
214
+ - is-active
215
+ - --quiet
216
+ - "{{ cloudfall_deploy_component.service.name }}.service"
217
+ register: cloudfall_deploy_unit_started
218
+ changed_when: false
219
+ when:
220
+ - not ansible_check_mode
221
+ - cloudfall_deploy_component.healthCheck.type == "none"
222
+
223
+ - name: Read the service unit restart count before settling
224
+ ansible.builtin.command:
225
+ argv:
226
+ - /usr/bin/systemctl
227
+ - show
228
+ - --property=NRestarts
229
+ - --value
230
+ - "{{ cloudfall_deploy_component.service.name }}.service"
231
+ register: cloudfall_deploy_unit_restarts_before
232
+ changed_when: false
233
+ when:
234
+ - not ansible_check_mode
235
+ - cloudfall_deploy_component.healthCheck.type == "none"
236
+
237
+ - name: Let the service unit settle
238
+ ansible.builtin.pause:
239
+ seconds: "{{ cloudfall_deploy_unit_settle_seconds }}"
240
+ when:
241
+ - not ansible_check_mode
242
+ - cloudfall_deploy_component.healthCheck.type == "none"
243
+
244
+ - name: Gate the release on a service unit still active after settling
245
+ ansible.builtin.command:
246
+ argv:
247
+ - /usr/bin/systemctl
248
+ - is-active
249
+ - --quiet
250
+ - "{{ cloudfall_deploy_component.service.name }}.service"
251
+ register: cloudfall_deploy_unit_settled
252
+ changed_when: false
253
+ when:
254
+ - not ansible_check_mode
255
+ - cloudfall_deploy_component.healthCheck.type == "none"
256
+
257
+ - name: Read the service unit restart count after settling
258
+ ansible.builtin.command:
259
+ argv:
260
+ - /usr/bin/systemctl
261
+ - show
262
+ - --property=NRestarts
263
+ - --value
264
+ - "{{ cloudfall_deploy_component.service.name }}.service"
265
+ register: cloudfall_deploy_unit_restarts_after
266
+ changed_when: false
267
+ when:
268
+ - not ansible_check_mode
269
+ - cloudfall_deploy_component.healthCheck.type == "none"
270
+
271
+ - name: Gate the release on a service unit that did not restart while settling
272
+ ansible.builtin.assert:
273
+ that:
274
+ - cloudfall_deploy_unit_restarts_before.stdout is match('^[0-9]+$')
275
+ - cloudfall_deploy_unit_restarts_after.stdout is match('^[0-9]+$')
276
+ - cloudfall_deploy_unit_restarts_after.stdout | int
277
+ == cloudfall_deploy_unit_restarts_before.stdout | int
278
+ fail_msg: >-
279
+ {{ cloudfall_deploy_component.service.name }}.service restarted
280
+ while settling (NRestarts {{
281
+ cloudfall_deploy_unit_restarts_before.stdout
282
+ }} to {{ cloudfall_deploy_unit_restarts_after.stdout }})
283
+ when:
284
+ - not ansible_check_mode
285
+ - cloudfall_deploy_component.healthCheck.type == "none"
207
286
  rescue:
208
287
  - name: Restore the previous release symlink
209
288
  ansible.builtin.file: