cloudfall 0.2.0__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 (282) hide show
  1. cloudfall-0.2.0/.ansible-lint +5 -0
  2. cloudfall-0.2.0/.github/workflows/ci.yml +53 -0
  3. cloudfall-0.2.0/.github/workflows/release.yml +78 -0
  4. cloudfall-0.2.0/.gitignore +290 -0
  5. cloudfall-0.2.0/ARCHITECTURE.md +263 -0
  6. cloudfall-0.2.0/CHANGELOG.md +140 -0
  7. cloudfall-0.2.0/CONTRIBUTING.md +65 -0
  8. cloudfall-0.2.0/LICENSE +661 -0
  9. cloudfall-0.2.0/MANIFESTO.md +94 -0
  10. cloudfall-0.2.0/PKG-INFO +408 -0
  11. cloudfall-0.2.0/README.md +382 -0
  12. cloudfall-0.2.0/ROADMAP.md +504 -0
  13. cloudfall-0.2.0/SECURITY.md +24 -0
  14. cloudfall-0.2.0/Taskfile.yml +348 -0
  15. cloudfall-0.2.0/config/README.md +58 -0
  16. cloudfall-0.2.0/config/examples/alert-rules/postgresql-down.yaml +12 -0
  17. cloudfall-0.2.0/config/examples/applications/crm.yaml +15 -0
  18. cloudfall-0.2.0/config/examples/components/crm-backend.yaml +50 -0
  19. cloudfall-0.2.0/config/examples/domains/crm-site.yaml +35 -0
  20. cloudfall-0.2.0/config/examples/logging-stacks/operations.yaml +77 -0
  21. cloudfall-0.2.0/config/examples/operator-policies/production.yaml +18 -0
  22. cloudfall-0.2.0/config/examples/server-types/debian-application.yaml +56 -0
  23. cloudfall-0.2.0/config/examples/servers/h1.yaml +18 -0
  24. cloudfall-0.2.0/config/examples/servers/h2.yaml +18 -0
  25. cloudfall-0.2.0/config/examples/services/postgresql-main.yaml +25 -0
  26. cloudfall-0.2.0/config/examples/services/redis-cache.yaml +23 -0
  27. cloudfall-0.2.0/config/examples/ssh-public-keys/example-admin.yaml +13 -0
  28. cloudfall-0.2.0/config/schemas/v1/alert-rule.schema.json +63 -0
  29. cloudfall-0.2.0/config/schemas/v1/application.schema.json +60 -0
  30. cloudfall-0.2.0/config/schemas/v1/artifact.schema.json +71 -0
  31. cloudfall-0.2.0/config/schemas/v1/backup-receipt.schema.json +58 -0
  32. cloudfall-0.2.0/config/schemas/v1/common.schema.json +115 -0
  33. cloudfall-0.2.0/config/schemas/v1/component.schema.json +234 -0
  34. cloudfall-0.2.0/config/schemas/v1/deployment-receipt.schema.json +53 -0
  35. cloudfall-0.2.0/config/schemas/v1/domain.schema.json +208 -0
  36. cloudfall-0.2.0/config/schemas/v1/environment-receipt.schema.json +55 -0
  37. cloudfall-0.2.0/config/schemas/v1/logging-stack.schema.json +473 -0
  38. cloudfall-0.2.0/config/schemas/v1/observed-domain.schema.json +158 -0
  39. cloudfall-0.2.0/config/schemas/v1/observed-server.schema.json +627 -0
  40. cloudfall-0.2.0/config/schemas/v1/operator-policy.schema.json +97 -0
  41. cloudfall-0.2.0/config/schemas/v1/operator-proposal.schema.json +253 -0
  42. cloudfall-0.2.0/config/schemas/v1/release-receipt.schema.json +66 -0
  43. cloudfall-0.2.0/config/schemas/v1/server-type.schema.json +289 -0
  44. cloudfall-0.2.0/config/schemas/v1/server.schema.json +146 -0
  45. cloudfall-0.2.0/config/schemas/v1/service.schema.json +222 -0
  46. cloudfall-0.2.0/config/schemas/v1/ssh-public-key.schema.json +55 -0
  47. cloudfall-0.2.0/config/tests/invalid/missing-server/applications/crm.yaml +10 -0
  48. cloudfall-0.2.0/config/tests/invalid/missing-server/components/crm-backend.yaml +35 -0
  49. cloudfall-0.2.0/config/tests/invalid/missing-server/server-types/debian-application.yaml +25 -0
  50. cloudfall-0.2.0/config/tests/invalid/missing-server/servers/h1.yaml +14 -0
  51. cloudfall-0.2.0/config/tests/invalid/schema/components/component-unknown-field.yaml +35 -0
  52. cloudfall-0.2.0/config/tests/observed/compliant/h1.json +143 -0
  53. cloudfall-0.2.0/config/tests/observed/compliant/h2.json +138 -0
  54. cloudfall-0.2.0/docs/ansible-context.md +82 -0
  55. cloudfall-0.2.0/docs/availability-design.md +194 -0
  56. cloudfall-0.2.0/docs/brownfield-design.md +490 -0
  57. cloudfall-0.2.0/docs/decisions/2026-09-14-no-ansible-replacement.md +105 -0
  58. cloudfall-0.2.0/docs/decisions/2026-09-14-no-nixos-base.md +79 -0
  59. cloudfall-0.2.0/docs/decisions/2026-09-14-no-rust-rewrite.md +63 -0
  60. cloudfall-0.2.0/docs/fleet-goals.md +162 -0
  61. cloudfall-0.2.0/docs/hybrid-storage-design.md +216 -0
  62. cloudfall-0.2.0/docs/landscape.md +191 -0
  63. cloudfall-0.2.0/docs/logging-service-guide.md +206 -0
  64. cloudfall-0.2.0/docs/neon-migration-guide.md +221 -0
  65. cloudfall-0.2.0/docs/new-server-storage-guide.md +657 -0
  66. cloudfall-0.2.0/docs/operator-guide.md +186 -0
  67. cloudfall-0.2.0/docs/proving-runs/2026-09-08-debian-container-m1.md +80 -0
  68. cloudfall-0.2.0/docs/proving-runs/2026-09-08-dns-cutover.md +60 -0
  69. cloudfall-0.2.0/docs/proving-runs/2026-09-08-guided-data-migration.md +70 -0
  70. cloudfall-0.2.0/docs/proving-runs/2026-09-08-hetzner-m1.md +72 -0
  71. cloudfall-0.2.0/docs/proving-runs/2026-09-08-hetzner-m2-m3.md +98 -0
  72. cloudfall-0.2.0/docs/proving-runs/2026-09-08-hetzner-m4-m5.md +77 -0
  73. cloudfall-0.2.0/docs/proving-runs/2026-09-08-render-cutover.md +63 -0
  74. cloudfall-0.2.0/docs/proving-runs/2026-09-10-hetzner-m7.md +80 -0
  75. cloudfall-0.2.0/docs/proving-runs/2026-09-10-hetzner-m8.md +78 -0
  76. cloudfall-0.2.0/docs/proving-runs/2026-09-10-hetzner-m9.md +71 -0
  77. cloudfall-0.2.0/docs/proving-runs/2026-09-11-hetzner-m10.md +91 -0
  78. cloudfall-0.2.0/docs/render-migration-guide.md +185 -0
  79. cloudfall-0.2.0/docs/secrets-guide.md +150 -0
  80. cloudfall-0.2.0/engine/README.md +230 -0
  81. cloudfall-0.2.0/engine/ansible/ansible.cfg +7 -0
  82. cloudfall-0.2.0/engine/ansible/playbooks/backup.yml +86 -0
  83. cloudfall-0.2.0/engine/ansible/playbooks/baseline.yml +35 -0
  84. cloudfall-0.2.0/engine/ansible/playbooks/bootstrap.yml +24 -0
  85. cloudfall-0.2.0/engine/ansible/playbooks/data.yml +28 -0
  86. cloudfall-0.2.0/engine/ansible/playbooks/deploy.yml +35 -0
  87. cloudfall-0.2.0/engine/ansible/playbooks/domains.yml +21 -0
  88. cloudfall-0.2.0/engine/ansible/playbooks/health.yml +31 -0
  89. cloudfall-0.2.0/engine/ansible/playbooks/inspect.yml +15 -0
  90. cloudfall-0.2.0/engine/ansible/playbooks/logging.yml +35 -0
  91. cloudfall-0.2.0/engine/ansible/playbooks/restart.yml +31 -0
  92. cloudfall-0.2.0/engine/ansible/playbooks/rollback.yml +33 -0
  93. cloudfall-0.2.0/engine/ansible/playbooks/services.yml +37 -0
  94. cloudfall-0.2.0/engine/ansible/playbooks/time.yml +9 -0
  95. cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/defaults/main.yml +2 -0
  96. cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/handlers/main.yml +5 -0
  97. cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/meta/argument_specs.yml +34 -0
  98. cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/meta/main.yml +13 -0
  99. cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/tasks/main.yml +59 -0
  100. cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/defaults/main.yml +11 -0
  101. cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/meta/argument_specs.yml +52 -0
  102. cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/meta/main.yml +13 -0
  103. cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/tasks/main.yml +99 -0
  104. cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/defaults/main.yml +15 -0
  105. cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/meta/argument_specs.yml +36 -0
  106. cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/meta/main.yml +13 -0
  107. cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/tasks/main.yml +201 -0
  108. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/defaults/main.yml +10 -0
  109. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/meta/argument_specs.yml +125 -0
  110. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/meta/main.yml +13 -0
  111. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/health.yml +3 -0
  112. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/health_gate.yml +37 -0
  113. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/main.yml +285 -0
  114. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/restart.yml +17 -0
  115. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/rollback.yml +52 -0
  116. cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/templates/component.service.j2 +24 -0
  117. cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/defaults/main.yml +2 -0
  118. cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/handlers/main.yml +5 -0
  119. cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/meta/argument_specs.yml +33 -0
  120. cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/meta/main.yml +13 -0
  121. cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/tasks/main.yml +51 -0
  122. cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/templates/nftables.conf.j2 +31 -0
  123. cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/defaults/main.yml +6 -0
  124. cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/meta/argument_specs.yml +24 -0
  125. cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/meta/main.yml +13 -0
  126. cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/tasks/main.yml +643 -0
  127. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/defaults/main.yml +6 -0
  128. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/handlers/main.yml +29 -0
  129. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/meta/argument_specs.yml +227 -0
  130. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/meta/main.yml +11 -0
  131. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/tasks/main.yml +487 -0
  132. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager-defaults.j2 +2 -0
  133. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager.yaml.j2 +33 -0
  134. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/grafana.ini.j2 +20 -0
  135. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/loki-datasource.yml.j2 +11 -0
  136. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/loki-systemd-override.conf.j2 +3 -0
  137. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/loki.yaml.j2 +49 -0
  138. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/nginx-loki-gateway.conf.j2 +45 -0
  139. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-datasource.yml.j2 +11 -0
  140. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-defaults.j2 +2 -0
  141. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-rules.yaml.j2 +20 -0
  142. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus.yaml.j2 +24 -0
  143. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/defaults/main.yml +7 -0
  144. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/handlers/main.yml +6 -0
  145. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/meta/argument_specs.yml +122 -0
  146. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/meta/main.yml +11 -0
  147. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/tasks/main.yml +224 -0
  148. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/templates/alloy-systemd-override.conf.j2 +3 -0
  149. cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/templates/config.alloy.j2 +127 -0
  150. cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/defaults/main.yml +4 -0
  151. cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/handlers/main.yml +5 -0
  152. cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/meta/argument_specs.yml +69 -0
  153. cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/meta/main.yml +13 -0
  154. cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/tasks/main.yml +212 -0
  155. cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/templates/domain-site.conf.j2 +54 -0
  156. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/defaults/main.yml +6 -0
  157. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/handlers/main.yml +9 -0
  158. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/meta/argument_specs.yml +91 -0
  159. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/meta/main.yml +13 -0
  160. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/tasks/main.yml +295 -0
  161. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/backup.service.j2 +11 -0
  162. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/backup.sh.j2 +17 -0
  163. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/backup.timer.j2 +10 -0
  164. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/cloudfall.conf.j2 +3 -0
  165. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.service.j2 +11 -0
  166. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.sh.j2 +27 -0
  167. cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.timer.j2 +10 -0
  168. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/defaults/main.yml +2 -0
  169. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/handlers/main.yml +6 -0
  170. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/meta/argument_specs.yml +68 -0
  171. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/meta/main.yml +13 -0
  172. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/tasks/main.yml +148 -0
  173. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/backup.service.j2 +11 -0
  174. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/backup.sh.j2 +14 -0
  175. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/backup.timer.j2 +10 -0
  176. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/cloudfall.conf.j2 +6 -0
  177. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/restore-check.service.j2 +11 -0
  178. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/restore-check.sh.j2 +15 -0
  179. cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/restore-check.timer.j2 +10 -0
  180. cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/defaults/main.yml +2 -0
  181. cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/meta/argument_specs.yml +10 -0
  182. cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/meta/main.yml +13 -0
  183. cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/tasks/main.yml +119 -0
  184. cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/defaults/main.yml +3 -0
  185. cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/meta/argument_specs.yml +8 -0
  186. cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/meta/main.yml +13 -0
  187. cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/tasks/main.yml +37 -0
  188. cloudfall-0.2.0/engine/src/cloudfall_engine/__init__.py +5 -0
  189. cloudfall-0.2.0/engine/src/cloudfall_engine/__main__.py +5 -0
  190. cloudfall-0.2.0/engine/src/cloudfall_engine/ansible_inventory.py +253 -0
  191. cloudfall-0.2.0/engine/src/cloudfall_engine/artifact.py +258 -0
  192. cloudfall-0.2.0/engine/src/cloudfall_engine/cli.py +273 -0
  193. cloudfall-0.2.0/engine/src/cloudfall_engine/playbook.py +175 -0
  194. cloudfall-0.2.0/engine/src/cloudfall_engine/py.typed +0 -0
  195. cloudfall-0.2.0/engine/tests/test_ansible_inventory.py +338 -0
  196. cloudfall-0.2.0/engine/tests/test_artifact_builder.py +137 -0
  197. cloudfall-0.2.0/engine/tests/test_deploy_templates.py +64 -0
  198. cloudfall-0.2.0/engine/tests/test_firewall_templates.py +70 -0
  199. cloudfall-0.2.0/engine/tests/test_logging_templates.py +300 -0
  200. cloudfall-0.2.0/engine/tests/test_nginx_site_templates.py +76 -0
  201. cloudfall-0.2.0/engine/tests/test_playbook.py +155 -0
  202. cloudfall-0.2.0/engine/tests/test_postgresql_templates.py +101 -0
  203. cloudfall-0.2.0/evaluations/cloudfall/README.md +43 -0
  204. cloudfall-0.2.0/evaluations/cloudfall/environment.md +61 -0
  205. cloudfall-0.2.0/evaluations/cloudfall/findings.md +26 -0
  206. cloudfall-0.2.0/evaluations/cloudfall/issues.md +78 -0
  207. cloudfall-0.2.0/evaluations/cloudfall/linkedin.md +33 -0
  208. cloudfall-0.2.0/evaluations/cloudfall/readiness.md +84 -0
  209. cloudfall-0.2.0/evaluations/cloudfall/report-agent-dev.md +1003 -0
  210. cloudfall-0.2.0/evaluations/cloudfall/report-dev.md +1048 -0
  211. cloudfall-0.2.0/evaluations/cloudfall/report-index.md +49 -0
  212. cloudfall-0.2.0/evaluations/cloudfall/report-issues.md +410 -0
  213. cloudfall-0.2.0/evaluations/cloudfall/report-runtime.md +74 -0
  214. cloudfall-0.2.0/evaluations/cloudfall/trace.md +1276 -0
  215. cloudfall-0.2.0/evaluations/cloudfall/x.md +45 -0
  216. cloudfall-0.2.0/examples/render-demo/README.md +12 -0
  217. cloudfall-0.2.0/examples/render-demo/pyproject.toml +16 -0
  218. cloudfall-0.2.0/examples/render-demo/render.yaml +29 -0
  219. cloudfall-0.2.0/examples/render-demo/src/demo/__init__.py +0 -0
  220. cloudfall-0.2.0/examples/render-demo/src/demo/asgi.py +40 -0
  221. cloudfall-0.2.0/examples/render-demo/uv.lock +104 -0
  222. cloudfall-0.2.0/pyproject.toml +90 -0
  223. cloudfall-0.2.0/sdk/README.md +148 -0
  224. cloudfall-0.2.0/sdk/src/cloudfall/__init__.py +19 -0
  225. cloudfall-0.2.0/sdk/src/cloudfall/__main__.py +5 -0
  226. cloudfall-0.2.0/sdk/src/cloudfall/agent_tools.py +868 -0
  227. cloudfall-0.2.0/sdk/src/cloudfall/arguments.py +92 -0
  228. cloudfall-0.2.0/sdk/src/cloudfall/audit.py +905 -0
  229. cloudfall-0.2.0/sdk/src/cloudfall/authoring.py +341 -0
  230. cloudfall-0.2.0/sdk/src/cloudfall/cli.py +1748 -0
  231. cloudfall-0.2.0/sdk/src/cloudfall/commands.py +257 -0
  232. cloudfall-0.2.0/sdk/src/cloudfall/cutover.py +370 -0
  233. cloudfall-0.2.0/sdk/src/cloudfall/dashboard.py +407 -0
  234. cloudfall-0.2.0/sdk/src/cloudfall/dashboard_server.py +201 -0
  235. cloudfall-0.2.0/sdk/src/cloudfall/domain.py +1157 -0
  236. cloudfall-0.2.0/sdk/src/cloudfall/importer.py +906 -0
  237. cloudfall-0.2.0/sdk/src/cloudfall/inventory.py +2116 -0
  238. cloudfall-0.2.0/sdk/src/cloudfall/lifecycle.py +874 -0
  239. cloudfall-0.2.0/sdk/src/cloudfall/mcp_server.py +704 -0
  240. cloudfall-0.2.0/sdk/src/cloudfall/migrate.py +730 -0
  241. cloudfall-0.2.0/sdk/src/cloudfall/observation.py +143 -0
  242. cloudfall-0.2.0/sdk/src/cloudfall/operations.py +1228 -0
  243. cloudfall-0.2.0/sdk/src/cloudfall/operator.py +1099 -0
  244. cloudfall-0.2.0/sdk/src/cloudfall/project.py +841 -0
  245. cloudfall-0.2.0/sdk/src/cloudfall/py.typed +0 -0
  246. cloudfall-0.2.0/sdk/src/cloudfall/render_api.py +338 -0
  247. cloudfall-0.2.0/sdk/src/cloudfall/resources.py +67 -0
  248. cloudfall-0.2.0/sdk/src/cloudfall/secrets.py +262 -0
  249. cloudfall-0.2.0/sdk/src/cloudfall/service_evidence.py +659 -0
  250. cloudfall-0.2.0/sdk/src/cloudfall/validation.py +1340 -0
  251. cloudfall-0.2.0/sdk/tests/test_agent_tools.py +357 -0
  252. cloudfall-0.2.0/sdk/tests/test_arguments.py +219 -0
  253. cloudfall-0.2.0/sdk/tests/test_audit.py +512 -0
  254. cloudfall-0.2.0/sdk/tests/test_authoring.py +204 -0
  255. cloudfall-0.2.0/sdk/tests/test_commands.py +84 -0
  256. cloudfall-0.2.0/sdk/tests/test_cutover.py +169 -0
  257. cloudfall-0.2.0/sdk/tests/test_dashboard_server.py +192 -0
  258. cloudfall-0.2.0/sdk/tests/test_data_migration.py +152 -0
  259. cloudfall-0.2.0/sdk/tests/test_importer.py +227 -0
  260. cloudfall-0.2.0/sdk/tests/test_inventory.py +221 -0
  261. cloudfall-0.2.0/sdk/tests/test_lifecycle.py +322 -0
  262. cloudfall-0.2.0/sdk/tests/test_migrate.py +249 -0
  263. cloudfall-0.2.0/sdk/tests/test_operations_dashboard.py +163 -0
  264. cloudfall-0.2.0/sdk/tests/test_operator.py +635 -0
  265. cloudfall-0.2.0/sdk/tests/test_project.py +568 -0
  266. cloudfall-0.2.0/sdk/tests/test_render_api.py +316 -0
  267. cloudfall-0.2.0/sdk/tests/test_resources.py +53 -0
  268. cloudfall-0.2.0/sdk/tests/test_secrets.py +155 -0
  269. cloudfall-0.2.0/sdk/tests/test_service_evidence.py +92 -0
  270. cloudfall-0.2.0/sdk/tests/test_validation.py +310 -0
  271. cloudfall-0.2.0/site/public/apple-touch-icon.png +0 -0
  272. cloudfall-0.2.0/site/public/favicon-32.png +0 -0
  273. cloudfall-0.2.0/site/public/favicon.svg +4 -0
  274. cloudfall-0.2.0/site/public/icon-192.png +0 -0
  275. cloudfall-0.2.0/site/public/icon-512.png +0 -0
  276. cloudfall-0.2.0/site/public/index.html +491 -0
  277. cloudfall-0.2.0/site/public/manifesto.html +367 -0
  278. cloudfall-0.2.0/site/public/og.png +0 -0
  279. cloudfall-0.2.0/site/public/site.webmanifest +23 -0
  280. cloudfall-0.2.0/site/src/worker.js +10 -0
  281. cloudfall-0.2.0/site/wrangler.jsonc +18 -0
  282. cloudfall-0.2.0/uv.lock +1198 -0
@@ -0,0 +1,5 @@
1
+ ---
2
+ profile: production
3
+ exclude_paths:
4
+ - .venv/
5
+ - tmp/
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: CI
3
+
4
+ "on":
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+
10
+ jobs:
11
+ checks:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v7
15
+
16
+ - uses: astral-sh/setup-uv@v10.1.0
17
+ with:
18
+ python-version: "3.14"
19
+
20
+ - name: Install dependencies
21
+ run: uv sync
22
+
23
+ - name: Lint
24
+ run: uv run ruff check .
25
+
26
+ - name: Type-check
27
+ run: uv run mypy
28
+
29
+ - name: Test
30
+ run: uv run pytest
31
+
32
+ - name: Validate example state
33
+ run: uv run cloudfall config validate --project config/examples
34
+
35
+ - name: Render Ansible inventory
36
+ run: >-
37
+ uv run cloudfall-engine inventory render --project config/examples
38
+ --output tmp/ansible-inventory.json
39
+
40
+ - name: Ansible syntax checks
41
+ env:
42
+ ANSIBLE_CONFIG: engine/ansible/ansible.cfg
43
+ run: |
44
+ for playbook in baseline bootstrap data deploy domains health inspect logging restart rollback services time; do
45
+ uv run ansible-playbook --syntax-check \
46
+ --inventory config/examples/tmp/ansible-inventory.json \
47
+ "engine/ansible/playbooks/${playbook}.yml"
48
+ done
49
+
50
+ - name: Ansible lint
51
+ env:
52
+ ANSIBLE_CONFIG: engine/ansible/ansible.cfg
53
+ run: uv run ansible-lint engine/ansible
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: Release
3
+
4
+ "on":
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+
13
+ # PyPI trusted publishing: the workflow proves its identity with a
14
+ # short-lived OIDC token, so no API token is stored anywhere.
15
+ environment: pypi
16
+ permissions:
17
+ id-token: write
18
+
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+
22
+ - uses: astral-sh/setup-uv@v10.1.0
23
+ with:
24
+ python-version: "3.14"
25
+
26
+ - name: Install dependencies
27
+ run: uv sync
28
+
29
+ - name: Check the tag matches the project version
30
+ run: |
31
+ version="$(uv run python -c 'import tomllib, pathlib; \
32
+ print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
33
+ tag="${GITHUB_REF_NAME#v}"
34
+ if [ "${version}" != "${tag}" ]; then
35
+ echo "tag ${GITHUB_REF_NAME} does not match project version ${version}" >&2
36
+ exit 1
37
+ fi
38
+
39
+ - name: Lint
40
+ run: uv run ruff check .
41
+
42
+ - name: Type-check
43
+ run: uv run mypy
44
+
45
+ - name: Test
46
+ run: uv run pytest
47
+
48
+ - name: Build
49
+ run: uv build
50
+
51
+ # The wheel carries the schema catalog and the Ansible engine as package
52
+ # data; a project installed from PyPI has no checkout to fall back on, so
53
+ # an empty bundle must fail the release rather than ship.
54
+ - name: Check the wheel carries the bundled schemas and engine
55
+ run: |
56
+ uv run python - <<'PY'
57
+ import sys
58
+ import zipfile
59
+ from pathlib import Path
60
+
61
+ wheel = next(Path("dist").glob("*.whl"))
62
+ names = zipfile.ZipFile(wheel).namelist()
63
+ for expected in ("cloudfall/_bundled/schemas/", "cloudfall_engine/_bundled/ansible/"):
64
+ if not any(name.startswith(expected) for name in names):
65
+ sys.exit(f"{wheel.name} carries nothing under {expected}")
66
+ PY
67
+
68
+ # The published artifact, exercised the way a user gets it: installed
69
+ # into an empty environment with no repository in sight.
70
+ - name: Smoke-test the built wheel
71
+ run: |
72
+ uv venv /tmp/wheel-check
73
+ VIRTUAL_ENV=/tmp/wheel-check uv pip install dist/*.whl
74
+ /tmp/wheel-check/bin/cloudfall config validate --project config/examples
75
+ /tmp/wheel-check/bin/cloudfall-engine inventory render --project config/examples
76
+
77
+ - name: Publish to PyPI
78
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,290 @@
1
+ ### VisualStudioCode template
2
+ .vscode/*
3
+ !.vscode/settings.json
4
+ !.vscode/tasks.json
5
+ !.vscode/launch.json
6
+ !.vscode/extensions.json
7
+ !.vscode/*.code-snippets
8
+
9
+ # Local History for Visual Studio Code
10
+ .history/
11
+
12
+ # Built Visual Studio Code Extensions
13
+ *.vsix
14
+
15
+ ### JetBrains template
16
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
17
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
18
+
19
+ # User-specific stuff
20
+ .idea/**/workspace.xml
21
+ .idea/**/tasks.xml
22
+ .idea/**/usage.statistics.xml
23
+ .idea/**/dictionaries
24
+ .idea/**/shelf
25
+
26
+ # AWS User-specific
27
+ .idea/**/aws.xml
28
+
29
+ # Generated files
30
+ .idea/**/contentModel.xml
31
+
32
+ # Sensitive or high-churn files
33
+ .idea/**/dataSources/
34
+ .idea/**/dataSources.ids
35
+ .idea/**/dataSources.local.xml
36
+ .idea/**/sqlDataSources.xml
37
+ .idea/**/dynamic.xml
38
+ .idea/**/uiDesigner.xml
39
+ .idea/**/dbnavigator.xml
40
+
41
+ # Gradle
42
+ .idea/**/gradle.xml
43
+ .idea/**/libraries
44
+
45
+ # Gradle and Maven with auto-import
46
+ # When using Gradle or Maven with auto-import, you should exclude module files,
47
+ # since they will be recreated, and may cause churn. Uncomment if using
48
+ # auto-import.
49
+ # .idea/artifacts
50
+ # .idea/compiler.xml
51
+ # .idea/jarRepositories.xml
52
+ # .idea/modules.xml
53
+ # .idea/*.iml
54
+ # .idea/modules
55
+ # *.iml
56
+ # *.ipr
57
+
58
+ # CMake
59
+ cmake-build-*/
60
+
61
+ # Mongo Explorer plugin
62
+ .idea/**/mongoSettings.xml
63
+
64
+ # File-based project format
65
+ *.iws
66
+
67
+ # IntelliJ
68
+ out/
69
+
70
+ # mpeltonen/sbt-idea plugin
71
+ .idea_modules/
72
+
73
+ # JIRA plugin
74
+ atlassian-ide-plugin.xml
75
+
76
+ # Cursive Clojure plugin
77
+ .idea/replstate.xml
78
+
79
+ # SonarLint plugin
80
+ .idea/sonarlint/
81
+
82
+ # Crashlytics plugin (for Android Studio and IntelliJ)
83
+ com_crashlytics_export_strings.xml
84
+ crashlytics.properties
85
+ crashlytics-build.properties
86
+ fabric.properties
87
+
88
+ # Editor-based Rest Client
89
+ .idea/httpRequests
90
+
91
+ # Android studio 3.1+ serialized cache file
92
+ .idea/caches/build_file_checksums.ser
93
+
94
+ ### macOS template
95
+ # General
96
+ .DS_Store
97
+ .AppleDouble
98
+ .LSOverride
99
+
100
+ # Icon must end with two \r
101
+ Icon
102
+
103
+ # Thumbnails
104
+ ._*
105
+
106
+ # Files that might appear in the root of a volume
107
+ .DocumentRevisions-V100
108
+ .fseventsd
109
+ .Spotlight-V100
110
+ .TemporaryItems
111
+ .Trashes
112
+ .VolumeIcon.icns
113
+ .com.apple.timemachine.donotpresent
114
+
115
+ # Directories potentially created on remote AFP share
116
+ .AppleDB
117
+ .AppleDesktop
118
+ Network Trash Folder
119
+ Temporary Items
120
+ .apdisk
121
+
122
+ ### Python template
123
+ # Byte-compiled / optimized / DLL files
124
+ __pycache__/
125
+ *.py[cod]
126
+ *$py.class
127
+
128
+ # C extensions
129
+ *.so
130
+
131
+ # Distribution / packaging
132
+ .Python
133
+ build/
134
+ develop-eggs/
135
+ dist/
136
+ downloads/
137
+ eggs/
138
+ .eggs/
139
+ lib/
140
+ lib64/
141
+ parts/
142
+ sdist/
143
+ var/
144
+ wheels/
145
+ share/python-wheels/
146
+ *.egg-info/
147
+ .installed.cfg
148
+ *.egg
149
+ MANIFEST
150
+
151
+ # PyInstaller
152
+ # Usually these files are written by a python script from a template
153
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
154
+ *.manifest
155
+ *.spec
156
+
157
+ # Installer logs
158
+ pip-log.txt
159
+ pip-delete-this-directory.txt
160
+
161
+ # Unit test / coverage reports
162
+ htmlcov/
163
+ .tox/
164
+ .nox/
165
+ .coverage
166
+ .coverage.*
167
+ .cache
168
+ nosetests.xml
169
+ coverage.xml
170
+ *.cover
171
+ *.py,cover
172
+ .hypothesis/
173
+ .pytest_cache/
174
+ cover/
175
+
176
+ # Translations
177
+ *.mo
178
+ *.pot
179
+
180
+ # Django stuff:
181
+ *.log
182
+ local_settings.py
183
+ db.sqlite3
184
+ db.sqlite3-journal
185
+
186
+ # Flask stuff:
187
+ instance/
188
+ .webassets-cache
189
+
190
+ # Scrapy stuff:
191
+ .scrapy
192
+
193
+ # Sphinx documentation
194
+ docs/_build/
195
+
196
+ # PyBuilder
197
+ .pybuilder/
198
+ target/
199
+
200
+ # Jupyter Notebook
201
+ .ipynb_checkpoints
202
+
203
+ # IPython
204
+ profile_default/
205
+ ipython_config.py
206
+
207
+ # pyenv
208
+ # For a library or package, you might want to ignore these files since the code is
209
+ # intended to run in multiple environments; otherwise, check them in:
210
+ # .python-version
211
+
212
+ # pipenv
213
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
214
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
215
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
216
+ # install all needed dependencies.
217
+ #Pipfile.lock
218
+
219
+ # poetry
220
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
221
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
222
+ # commonly ignored for libraries.
223
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
224
+ #poetry.lock
225
+
226
+ # pdm
227
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
228
+ #pdm.lock
229
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
230
+ # in version control.
231
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
232
+ .pdm.toml
233
+ .pdm-python
234
+ .pdm-build/
235
+
236
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
237
+ __pypackages__/
238
+
239
+ # Celery stuff
240
+ celerybeat-schedule
241
+ celerybeat.pid
242
+
243
+ # SageMath parsed files
244
+ *.sage.py
245
+
246
+ # Environments
247
+ .env
248
+ .venv
249
+ env/
250
+ venv/
251
+ ENV/
252
+ env.bak/
253
+ venv.bak/
254
+
255
+ # Spyder project settings
256
+ .spyderproject
257
+ .spyproject
258
+
259
+ # Rope project settings
260
+ .ropeproject
261
+
262
+ # mypy
263
+ .mypy_cache/
264
+ .dmypy.json
265
+ dmypy.json
266
+
267
+ # Pyre type checker
268
+ .pyre/
269
+
270
+ # pytype static type analyzer
271
+ .pytype/
272
+
273
+ # Cython debug symbols
274
+ cython_debug/
275
+
276
+ # PyCharm
277
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
278
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
279
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
280
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
281
+ #.idea/
282
+
283
+ # Atlas
284
+ .ruff_cache/
285
+ tmp/
286
+ .idea/
287
+ memory/
288
+
289
+ # Wrangler local state
290
+ site/.wrangler/
@@ -0,0 +1,263 @@
1
+ # Architecture
2
+
3
+ Cloudfall is the operator's record for a small fleet run by an AI agent.
4
+ The agent decides, Ansible executes, and Cloudfall sits between them: it
5
+ declares what the agent may do, checks every result, and keeps the evidence
6
+ behind every decision. Ansible is the hand, the agent is the brain,
7
+ Cloudfall is the conscience.
8
+
9
+ It was built wedge-first: take a SaaS off a cloud PaaS such as Render and
10
+ run it on one fresh Debian host with a hardened baseline, monitoring,
11
+ deploys, and rollback, then let an always-on operator run it there. The
12
+ architecture is designed for the goal behind the wedge: an agent safely
13
+ operating 10–100 standalone SaaS applications across a fleet of dedicated
14
+ Debian servers without Kubernetes (see the [fleet goals](docs/fleet-goals.md)),
15
+ on a config the team already owns. This document describes the durable
16
+ design; [`ROADMAP.md`](ROADMAP.md) is the authoritative source for what is
17
+ implemented today, and the [brownfield design](docs/brownfield-design.md)
18
+ describes the proposed next step, none of which is built.
19
+
20
+ Every layer keeps the same invariants: no action without declared config, no
21
+ status without evidence, no compliance without audit, no decision without a
22
+ record.
23
+
24
+ ## Control plane stack
25
+
26
+ ```text
27
+ Human / AI agent ──edits──► Config (YAML)
28
+ Human / AI agent ──runs───► CLI / MCP / Python API ──► Gate ──► Execution engine ──► Servers
29
+ │ │
30
+ └──────────── Record ◄── Verify ◄───┘
31
+ ```
32
+
33
+ The config is a store, not a pipeline stage: humans and agents edit it
34
+ directly, the CLI validates and reads it, and the engine converges servers
35
+ to it. The agent operates through a stable CLI, the `cloudfall-mcp` server
36
+ and a Python API instead of inventing shell commands or discovering
37
+ infrastructure over SSH. Every mutating operation goes through the engine's
38
+ explicit playbook contracts, waits behind a gate the agent does not control
39
+ (a confirmation handshake, or an `OperatorPolicy` that licenses the
40
+ operation class), and produces receipts. Status is derived from validated
41
+ observations, never inferred.
42
+
43
+ The record is the durable output. Every operator decision is written as a
44
+ schema-validated receipt that holds the evidence it was based on, the
45
+ diagnosis, the exact operation, who approved it and the outcome; deploys,
46
+ backups, restore drills and data migrations write receipts of their own.
47
+ The agent's own log says a tool was called. The record says what the fleet
48
+ looked like when it was, and it is what licenses autonomy later.
49
+
50
+ The loop the agent runs is fixed: observe (read evidence), decide (pick an
51
+ operation and targets, state why), preview (check mode and diff), approve
52
+ (gate by risk), run (execute through the engine), verify (check the result
53
+ and update the evidence).
54
+
55
+ ## Project
56
+
57
+ A project is the unit Cloudfall operates: one directory, usually a private
58
+ repository, holding everything needed to run one user's stack.
59
+
60
+ - **Fleet** — the machines: `Server`, `ServerType`, and `SshPublicKey`
61
+ resources
62
+ - **Applications** — the workloads: `Application`, `Component`, `Service`,
63
+ and `Domain` resources
64
+ - **Operations** — the declarations that run them: `AlertRule`,
65
+ `OperatorPolicy`, and `LoggingStack` resources
66
+ - **Pin** — a `pyproject.toml` that installs Cloudfall as a package at one
67
+ exact commit, so the project and the platform version move together
68
+
69
+ Every `cloudfall` command runs inside a project: the current directory when
70
+ it is one, or the directory named by `--project` or by the
71
+ `CLOUDFALL_PROJECT` environment variable. Relative paths, including the
72
+ `tmp/` defaults, resolve against the project, as if the command had been
73
+ started there.
74
+ Resources live in one directory per kind (`servers/`, `components/`, ...),
75
+ and only those directories are read as resources; a resource in the wrong
76
+ directory is a validation error. Everything else in the project is the
77
+ user's: playbooks and roles of their own, encrypted secrets, docs, tooling.
78
+ Runtime state (observations, receipts, rendered inventory, built artifacts,
79
+ rendered environment files, the persisted migration plan) lands under `tmp/`
80
+ and is never committed. `cloudfall init` lays out a new project and
81
+ `cloudfall add` declares its first fleet resources.
82
+
83
+ Proposed, not built: for a team that already runs Ansible, the project is
84
+ their existing repository. The fleet is read from their inventory instead of
85
+ `Server` resources, Cloudfall's declarations live under one `cloudfall` key
86
+ in their `group_vars` and `host_vars`, and their playbooks are registered as
87
+ operations. One config, theirs; no import and no second copy. See the
88
+ [brownfield design](docs/brownfield-design.md).
89
+
90
+ ## Module boundaries
91
+
92
+ Cloudfall is a monorepo with three architectural modules:
93
+
94
+ - [`config/`](config/README.md) — the config: declarative YAML resources and
95
+ their JSON Schemas
96
+ - [`sdk/`](sdk/README.md) — the `cloudfall` CLI and Python API consumed by
97
+ agents and tooling
98
+ - [`engine/`](engine/README.md) — internal execution machinery: Ansible-based
99
+ execution of explicit plans
100
+
101
+ The boundaries are strict even inside one repository: the config contains no
102
+ execution logic, the CLI reads and validates the config without Ansible
103
+ internals, the engine never silently rewrites the config, and external entry
104
+ points call the CLI rather than Ansible directly. Mutating operations execute
105
+ through the engine's command-line and playbook contracts, never its
106
+ internals. The modules may split into separate repositories later if
107
+ independent release cycles or access control require it.
108
+
109
+ ## Config
110
+
111
+ The config is declarative only, organized as typed resources validated
112
+ against versioned JSON Schemas: servers, server types, applications,
113
+ components, services, domains, logging stacks, alert rules, operator
114
+ policy, and SSH public keys.
115
+
116
+ The config never contains secret values; schemas and validation reject them.
117
+ Secrets exist as references, rendered from a sops/age-encrypted secrets
118
+ directory into environment files under the project's `tmp/`, never into a
119
+ resource, and consumed by systemd via `EnvironmentFile`.
120
+
121
+ The typed resources are today's config, not the point. The point is that
122
+ there is one description of the fleet, the team owns it, it is plain files
123
+ in a repository, and nothing rewrites it silently. The brownfield design
124
+ keeps every one of those properties while moving the description into the
125
+ team's own Ansible inventory; the schemas then validate the `cloudfall`
126
+ block instead of standalone documents.
127
+
128
+ ## Application and component model
129
+
130
+ An application is an independent SaaS
131
+ product that composes components; a component (frontend, backend, worker,
132
+ scheduler) owns its own deployment. Components are named `crm-backend` style
133
+ rather than by path, and each may deploy to one or more servers.
134
+
135
+ On disk, each application owns a Linux user and a `/srv/apps/<application>/`
136
+ directory with one subdirectory per component. Releases unpack into
137
+ `releases/` and a `current` symlink points at the active release, which is
138
+ what makes rollback a symlink switch.
139
+
140
+ Components come from one repository each by preference, with monorepos also
141
+ supported.
142
+
143
+ ## Deployment strategy
144
+
145
+ Artifacts are built on the management host — the machine running Cloudfall,
146
+ which is a workstation for one-off migrations or a small always-on host
147
+ once the operator runs persistently — never on production servers:
148
+
149
+ ```text
150
+ Git ref → build artifact (hashed tarball + release metadata) → digest-verified
151
+ transfer → unpack into releases/ → locked dependency materialization →
152
+ environment file → systemd unit → health-check gate → symlink switch
153
+ ```
154
+
155
+ A failed health check triggers automatic rollback to the previous release.
156
+ Releases are retained until a cleanup policy removes them, so explicit
157
+ rollback to any retained release stays available. Deployment approval is
158
+ automatic or manual per application.
159
+
160
+ ## Supported workloads
161
+
162
+ Applications run as native systemd units on long-lived Debian hosts, not
163
+ in containers. The primary stacks are Python (uv, Gunicorn/Uvicorn, Celery)
164
+ and Node.js; anything that runs as a systemd service with a health endpoint
165
+ fits the model.
166
+
167
+ ## Infrastructure model
168
+
169
+ Servers are traditional long-lived Debian hosts (not immutable), described
170
+ by reusable `ServerType` resources; bare-metal server types
171
+ add software RAID1 and the hybrid storage layout, while cloud VPS types do
172
+ not. Any server can run any declared service or component:
173
+
174
+ - Infrastructure services (PostgreSQL and Redis today; MySQL, Elasticsearch,
175
+ and friends as the catalog grows) stay pinned to declared servers
176
+ - Application components are movable: reassigning `crm-backend` from `h1,h2`
177
+ to `h3` is a config change followed by convergence
178
+ - Failover is manual with easy reassignment rather than automated
179
+ orchestration; the planned PostgreSQL high-availability formation (M14 on
180
+ the roadmap) amends this at the database layer only, where a standby may
181
+ be promoted automatically but an operator never initiates a promotion
182
+ without explicit confirmation
183
+
184
+ ## Networking
185
+
186
+ Nginx terminates TLS with virtual hosts rendered from `Domain` resources and
187
+ Let's Encrypt certificates. Cloudflare proxying is used where appropriate,
188
+ and future load balancing is Cloudflare plus Nginx, with HAProxy optional
189
+ later.
190
+
191
+ ## Operations layer
192
+
193
+ Operating servers is the same evidence discipline running continuously:
194
+
195
+ - **Alerting** — alert rules and notification channels are typed config
196
+ rendered into the Loki/Grafana/Alloy stack; a firing alert is evidence,
197
+ exposed over a read-only mTLS route on the same gateway that receives
198
+ logs and metrics
199
+ - **Operator** — an always-on management-host process watches alerts and
200
+ scheduled drift audits, writes every diagnosis as a schema-validated
201
+ proposal receipt (trigger evidence, diagnosis, exact operation, outcome),
202
+ and executes only existing engine entry points
203
+ - **Graduated autonomy** — autonomy is granted per operation class by a
204
+ declared `OperatorPolicy` and earned by verified receipt history, never
205
+ globally; DNS cutover, data deletion, and database promotion stay behind
206
+ explicit confirmation regardless of autonomy level
207
+ - **Record** — receipts are the audit log: schema-validated, written under
208
+ the project's `tmp/`, one per decision or operation, holding the evidence,
209
+ the action, the approver and the outcome. "Why did the agent do that" is
210
+ answered from the record, not from the agent's memory
211
+ - **Verify** — a run is not an outcome. Deploys are gated on health, backups
212
+ on a timer-driven restore drill, migrations on row counts, audits on
213
+ observed drift. The brownfield design generalises this into a verify step
214
+ declared on every operation
215
+
216
+ ## Direction: the team's Ansible as the config
217
+
218
+ The wedge proves the model on one host that Cloudfall set up. Most teams
219
+ that would use the record already have a fleet and an Ansible repository.
220
+ The [brownfield design](docs/brownfield-design.md) re-roots the same
221
+ machinery on that repository, in this build order:
222
+
223
+ - **Operations catalog** — each of the team's playbooks becomes a declared
224
+ tool with typed inputs, a risk level exported as MCP tool annotations,
225
+ preconditions and a verify step
226
+ - **Approval and audit** — the gate and the audit entry, unchanged in
227
+ principle from the operator above, applied to every declared operation
228
+ - **Agent surface** — the same CLI and MCP server, with the tool list
229
+ generated from the catalog
230
+ - **Fleet reader** — hosts, groups and merged variables read through
231
+ ansible-core in process, with the `cloudfall` block as the only addition
232
+ - **Observation, safe edits, starter roles** — last; the existing engine
233
+ roles become optional operations for teams with no baseline yet
234
+
235
+ None of this is implemented. The module boundaries above hold: the reader
236
+ is one wrapper module around ansible-core, and mutating operations still
237
+ execute through the engine's contracts.
238
+
239
+ ## Long-term vision: fleet operation
240
+
241
+ The same config, record, and deploy machinery is designed to extend to
242
+ fleet operation without architectural change; the
243
+ [fleet goals](docs/fleet-goals.md) state the requirements this must deliver
244
+ and their current status:
245
+
246
+ - **Catalog breadth** — MySQL, Elasticsearch, RabbitMQ, and Node runtime
247
+ services following the PostgreSQL and Redis pattern: pinned installs,
248
+ loopback-only binds, backup policies, audited evidence
249
+ - **Multi-server assignment at scale** — components spread across many
250
+ servers and moved by editing the config; the model exists, fleet-scale
251
+ operation is the unproven part
252
+ - **Managed secrets backends** — the sops/age provider boundary admits a
253
+ central secrets manager later, keeping the platform/application/component
254
+ scope hierarchy
255
+ - **Fleet observability** — per-service exporters beyond PostgreSQL and
256
+ Redis, and alert rules spanning more than one host
257
+ - **PostgreSQL high availability** — a declared replication, failover, and
258
+ point-in-time recovery (M13) and failover formation (M14 on the roadmap)
259
+
260
+ ## Status
261
+
262
+ Implementation status, milestone exit criteria, and what remains before the
263
+ wedge is proven live are tracked in [`ROADMAP.md`](ROADMAP.md).