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.
- cloudfall-0.2.0/.ansible-lint +5 -0
- cloudfall-0.2.0/.github/workflows/ci.yml +53 -0
- cloudfall-0.2.0/.github/workflows/release.yml +78 -0
- cloudfall-0.2.0/.gitignore +290 -0
- cloudfall-0.2.0/ARCHITECTURE.md +263 -0
- cloudfall-0.2.0/CHANGELOG.md +140 -0
- cloudfall-0.2.0/CONTRIBUTING.md +65 -0
- cloudfall-0.2.0/LICENSE +661 -0
- cloudfall-0.2.0/MANIFESTO.md +94 -0
- cloudfall-0.2.0/PKG-INFO +408 -0
- cloudfall-0.2.0/README.md +382 -0
- cloudfall-0.2.0/ROADMAP.md +504 -0
- cloudfall-0.2.0/SECURITY.md +24 -0
- cloudfall-0.2.0/Taskfile.yml +348 -0
- cloudfall-0.2.0/config/README.md +58 -0
- cloudfall-0.2.0/config/examples/alert-rules/postgresql-down.yaml +12 -0
- cloudfall-0.2.0/config/examples/applications/crm.yaml +15 -0
- cloudfall-0.2.0/config/examples/components/crm-backend.yaml +50 -0
- cloudfall-0.2.0/config/examples/domains/crm-site.yaml +35 -0
- cloudfall-0.2.0/config/examples/logging-stacks/operations.yaml +77 -0
- cloudfall-0.2.0/config/examples/operator-policies/production.yaml +18 -0
- cloudfall-0.2.0/config/examples/server-types/debian-application.yaml +56 -0
- cloudfall-0.2.0/config/examples/servers/h1.yaml +18 -0
- cloudfall-0.2.0/config/examples/servers/h2.yaml +18 -0
- cloudfall-0.2.0/config/examples/services/postgresql-main.yaml +25 -0
- cloudfall-0.2.0/config/examples/services/redis-cache.yaml +23 -0
- cloudfall-0.2.0/config/examples/ssh-public-keys/example-admin.yaml +13 -0
- cloudfall-0.2.0/config/schemas/v1/alert-rule.schema.json +63 -0
- cloudfall-0.2.0/config/schemas/v1/application.schema.json +60 -0
- cloudfall-0.2.0/config/schemas/v1/artifact.schema.json +71 -0
- cloudfall-0.2.0/config/schemas/v1/backup-receipt.schema.json +58 -0
- cloudfall-0.2.0/config/schemas/v1/common.schema.json +115 -0
- cloudfall-0.2.0/config/schemas/v1/component.schema.json +234 -0
- cloudfall-0.2.0/config/schemas/v1/deployment-receipt.schema.json +53 -0
- cloudfall-0.2.0/config/schemas/v1/domain.schema.json +208 -0
- cloudfall-0.2.0/config/schemas/v1/environment-receipt.schema.json +55 -0
- cloudfall-0.2.0/config/schemas/v1/logging-stack.schema.json +473 -0
- cloudfall-0.2.0/config/schemas/v1/observed-domain.schema.json +158 -0
- cloudfall-0.2.0/config/schemas/v1/observed-server.schema.json +627 -0
- cloudfall-0.2.0/config/schemas/v1/operator-policy.schema.json +97 -0
- cloudfall-0.2.0/config/schemas/v1/operator-proposal.schema.json +253 -0
- cloudfall-0.2.0/config/schemas/v1/release-receipt.schema.json +66 -0
- cloudfall-0.2.0/config/schemas/v1/server-type.schema.json +289 -0
- cloudfall-0.2.0/config/schemas/v1/server.schema.json +146 -0
- cloudfall-0.2.0/config/schemas/v1/service.schema.json +222 -0
- cloudfall-0.2.0/config/schemas/v1/ssh-public-key.schema.json +55 -0
- cloudfall-0.2.0/config/tests/invalid/missing-server/applications/crm.yaml +10 -0
- cloudfall-0.2.0/config/tests/invalid/missing-server/components/crm-backend.yaml +35 -0
- cloudfall-0.2.0/config/tests/invalid/missing-server/server-types/debian-application.yaml +25 -0
- cloudfall-0.2.0/config/tests/invalid/missing-server/servers/h1.yaml +14 -0
- cloudfall-0.2.0/config/tests/invalid/schema/components/component-unknown-field.yaml +35 -0
- cloudfall-0.2.0/config/tests/observed/compliant/h1.json +143 -0
- cloudfall-0.2.0/config/tests/observed/compliant/h2.json +138 -0
- cloudfall-0.2.0/docs/ansible-context.md +82 -0
- cloudfall-0.2.0/docs/availability-design.md +194 -0
- cloudfall-0.2.0/docs/brownfield-design.md +490 -0
- cloudfall-0.2.0/docs/decisions/2026-09-14-no-ansible-replacement.md +105 -0
- cloudfall-0.2.0/docs/decisions/2026-09-14-no-nixos-base.md +79 -0
- cloudfall-0.2.0/docs/decisions/2026-09-14-no-rust-rewrite.md +63 -0
- cloudfall-0.2.0/docs/fleet-goals.md +162 -0
- cloudfall-0.2.0/docs/hybrid-storage-design.md +216 -0
- cloudfall-0.2.0/docs/landscape.md +191 -0
- cloudfall-0.2.0/docs/logging-service-guide.md +206 -0
- cloudfall-0.2.0/docs/neon-migration-guide.md +221 -0
- cloudfall-0.2.0/docs/new-server-storage-guide.md +657 -0
- cloudfall-0.2.0/docs/operator-guide.md +186 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-debian-container-m1.md +80 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-dns-cutover.md +60 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-guided-data-migration.md +70 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-hetzner-m1.md +72 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-hetzner-m2-m3.md +98 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-hetzner-m4-m5.md +77 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-08-render-cutover.md +63 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-10-hetzner-m7.md +80 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-10-hetzner-m8.md +78 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-10-hetzner-m9.md +71 -0
- cloudfall-0.2.0/docs/proving-runs/2026-09-11-hetzner-m10.md +91 -0
- cloudfall-0.2.0/docs/render-migration-guide.md +185 -0
- cloudfall-0.2.0/docs/secrets-guide.md +150 -0
- cloudfall-0.2.0/engine/README.md +230 -0
- cloudfall-0.2.0/engine/ansible/ansible.cfg +7 -0
- cloudfall-0.2.0/engine/ansible/playbooks/backup.yml +86 -0
- cloudfall-0.2.0/engine/ansible/playbooks/baseline.yml +35 -0
- cloudfall-0.2.0/engine/ansible/playbooks/bootstrap.yml +24 -0
- cloudfall-0.2.0/engine/ansible/playbooks/data.yml +28 -0
- cloudfall-0.2.0/engine/ansible/playbooks/deploy.yml +35 -0
- cloudfall-0.2.0/engine/ansible/playbooks/domains.yml +21 -0
- cloudfall-0.2.0/engine/ansible/playbooks/health.yml +31 -0
- cloudfall-0.2.0/engine/ansible/playbooks/inspect.yml +15 -0
- cloudfall-0.2.0/engine/ansible/playbooks/logging.yml +35 -0
- cloudfall-0.2.0/engine/ansible/playbooks/restart.yml +31 -0
- cloudfall-0.2.0/engine/ansible/playbooks/rollback.yml +33 -0
- cloudfall-0.2.0/engine/ansible/playbooks/services.yml +37 -0
- cloudfall-0.2.0/engine/ansible/playbooks/time.yml +9 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/defaults/main.yml +2 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/handlers/main.yml +5 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/meta/argument_specs.yml +34 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_access/tasks/main.yml +59 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/defaults/main.yml +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/meta/argument_specs.yml +52 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_bootstrap/tasks/main.yml +99 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/defaults/main.yml +15 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/meta/argument_specs.yml +36 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_data_migration/tasks/main.yml +201 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/defaults/main.yml +10 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/meta/argument_specs.yml +125 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/health.yml +3 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/health_gate.yml +37 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/main.yml +285 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/restart.yml +17 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/tasks/rollback.yml +52 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_deploy/templates/component.service.j2 +24 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/defaults/main.yml +2 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/handlers/main.yml +5 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/meta/argument_specs.yml +33 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/tasks/main.yml +51 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_firewall/templates/nftables.conf.j2 +31 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/defaults/main.yml +6 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/meta/argument_specs.yml +24 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_inspect/tasks/main.yml +643 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/defaults/main.yml +6 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/handlers/main.yml +29 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/meta/argument_specs.yml +227 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/meta/main.yml +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/tasks/main.yml +487 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager-defaults.j2 +2 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager.yaml.j2 +33 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/grafana.ini.j2 +20 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/loki-datasource.yml.j2 +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/loki-systemd-override.conf.j2 +3 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/loki.yaml.j2 +49 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/nginx-loki-gateway.conf.j2 +45 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-datasource.yml.j2 +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-defaults.j2 +2 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-rules.yaml.j2 +20 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus.yaml.j2 +24 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/defaults/main.yml +7 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/handlers/main.yml +6 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/meta/argument_specs.yml +122 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/meta/main.yml +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/tasks/main.yml +224 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/templates/alloy-systemd-override.conf.j2 +3 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_logging_collector/templates/config.alloy.j2 +127 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/defaults/main.yml +4 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/handlers/main.yml +5 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/meta/argument_specs.yml +69 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/tasks/main.yml +212 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_nginx_site/templates/domain-site.conf.j2 +54 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/defaults/main.yml +6 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/handlers/main.yml +9 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/meta/argument_specs.yml +91 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/tasks/main.yml +295 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/backup.service.j2 +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/backup.sh.j2 +17 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/backup.timer.j2 +10 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/cloudfall.conf.j2 +3 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.service.j2 +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.sh.j2 +27 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.timer.j2 +10 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/defaults/main.yml +2 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/handlers/main.yml +6 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/meta/argument_specs.yml +68 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/tasks/main.yml +148 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/backup.service.j2 +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/backup.sh.j2 +14 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/backup.timer.j2 +10 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/cloudfall.conf.j2 +6 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/restore-check.service.j2 +11 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/restore-check.sh.j2 +15 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_redis/templates/restore-check.timer.j2 +10 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/defaults/main.yml +2 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/meta/argument_specs.yml +10 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_time/tasks/main.yml +119 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/defaults/main.yml +3 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/meta/argument_specs.yml +8 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/meta/main.yml +13 -0
- cloudfall-0.2.0/engine/ansible/roles/cloudfall_unattended_upgrades/tasks/main.yml +37 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/__init__.py +5 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/__main__.py +5 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/ansible_inventory.py +253 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/artifact.py +258 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/cli.py +273 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/playbook.py +175 -0
- cloudfall-0.2.0/engine/src/cloudfall_engine/py.typed +0 -0
- cloudfall-0.2.0/engine/tests/test_ansible_inventory.py +338 -0
- cloudfall-0.2.0/engine/tests/test_artifact_builder.py +137 -0
- cloudfall-0.2.0/engine/tests/test_deploy_templates.py +64 -0
- cloudfall-0.2.0/engine/tests/test_firewall_templates.py +70 -0
- cloudfall-0.2.0/engine/tests/test_logging_templates.py +300 -0
- cloudfall-0.2.0/engine/tests/test_nginx_site_templates.py +76 -0
- cloudfall-0.2.0/engine/tests/test_playbook.py +155 -0
- cloudfall-0.2.0/engine/tests/test_postgresql_templates.py +101 -0
- cloudfall-0.2.0/evaluations/cloudfall/README.md +43 -0
- cloudfall-0.2.0/evaluations/cloudfall/environment.md +61 -0
- cloudfall-0.2.0/evaluations/cloudfall/findings.md +26 -0
- cloudfall-0.2.0/evaluations/cloudfall/issues.md +78 -0
- cloudfall-0.2.0/evaluations/cloudfall/linkedin.md +33 -0
- cloudfall-0.2.0/evaluations/cloudfall/readiness.md +84 -0
- cloudfall-0.2.0/evaluations/cloudfall/report-agent-dev.md +1003 -0
- cloudfall-0.2.0/evaluations/cloudfall/report-dev.md +1048 -0
- cloudfall-0.2.0/evaluations/cloudfall/report-index.md +49 -0
- cloudfall-0.2.0/evaluations/cloudfall/report-issues.md +410 -0
- cloudfall-0.2.0/evaluations/cloudfall/report-runtime.md +74 -0
- cloudfall-0.2.0/evaluations/cloudfall/trace.md +1276 -0
- cloudfall-0.2.0/evaluations/cloudfall/x.md +45 -0
- cloudfall-0.2.0/examples/render-demo/README.md +12 -0
- cloudfall-0.2.0/examples/render-demo/pyproject.toml +16 -0
- cloudfall-0.2.0/examples/render-demo/render.yaml +29 -0
- cloudfall-0.2.0/examples/render-demo/src/demo/__init__.py +0 -0
- cloudfall-0.2.0/examples/render-demo/src/demo/asgi.py +40 -0
- cloudfall-0.2.0/examples/render-demo/uv.lock +104 -0
- cloudfall-0.2.0/pyproject.toml +90 -0
- cloudfall-0.2.0/sdk/README.md +148 -0
- cloudfall-0.2.0/sdk/src/cloudfall/__init__.py +19 -0
- cloudfall-0.2.0/sdk/src/cloudfall/__main__.py +5 -0
- cloudfall-0.2.0/sdk/src/cloudfall/agent_tools.py +868 -0
- cloudfall-0.2.0/sdk/src/cloudfall/arguments.py +92 -0
- cloudfall-0.2.0/sdk/src/cloudfall/audit.py +905 -0
- cloudfall-0.2.0/sdk/src/cloudfall/authoring.py +341 -0
- cloudfall-0.2.0/sdk/src/cloudfall/cli.py +1748 -0
- cloudfall-0.2.0/sdk/src/cloudfall/commands.py +257 -0
- cloudfall-0.2.0/sdk/src/cloudfall/cutover.py +370 -0
- cloudfall-0.2.0/sdk/src/cloudfall/dashboard.py +407 -0
- cloudfall-0.2.0/sdk/src/cloudfall/dashboard_server.py +201 -0
- cloudfall-0.2.0/sdk/src/cloudfall/domain.py +1157 -0
- cloudfall-0.2.0/sdk/src/cloudfall/importer.py +906 -0
- cloudfall-0.2.0/sdk/src/cloudfall/inventory.py +2116 -0
- cloudfall-0.2.0/sdk/src/cloudfall/lifecycle.py +874 -0
- cloudfall-0.2.0/sdk/src/cloudfall/mcp_server.py +704 -0
- cloudfall-0.2.0/sdk/src/cloudfall/migrate.py +730 -0
- cloudfall-0.2.0/sdk/src/cloudfall/observation.py +143 -0
- cloudfall-0.2.0/sdk/src/cloudfall/operations.py +1228 -0
- cloudfall-0.2.0/sdk/src/cloudfall/operator.py +1099 -0
- cloudfall-0.2.0/sdk/src/cloudfall/project.py +841 -0
- cloudfall-0.2.0/sdk/src/cloudfall/py.typed +0 -0
- cloudfall-0.2.0/sdk/src/cloudfall/render_api.py +338 -0
- cloudfall-0.2.0/sdk/src/cloudfall/resources.py +67 -0
- cloudfall-0.2.0/sdk/src/cloudfall/secrets.py +262 -0
- cloudfall-0.2.0/sdk/src/cloudfall/service_evidence.py +659 -0
- cloudfall-0.2.0/sdk/src/cloudfall/validation.py +1340 -0
- cloudfall-0.2.0/sdk/tests/test_agent_tools.py +357 -0
- cloudfall-0.2.0/sdk/tests/test_arguments.py +219 -0
- cloudfall-0.2.0/sdk/tests/test_audit.py +512 -0
- cloudfall-0.2.0/sdk/tests/test_authoring.py +204 -0
- cloudfall-0.2.0/sdk/tests/test_commands.py +84 -0
- cloudfall-0.2.0/sdk/tests/test_cutover.py +169 -0
- cloudfall-0.2.0/sdk/tests/test_dashboard_server.py +192 -0
- cloudfall-0.2.0/sdk/tests/test_data_migration.py +152 -0
- cloudfall-0.2.0/sdk/tests/test_importer.py +227 -0
- cloudfall-0.2.0/sdk/tests/test_inventory.py +221 -0
- cloudfall-0.2.0/sdk/tests/test_lifecycle.py +322 -0
- cloudfall-0.2.0/sdk/tests/test_migrate.py +249 -0
- cloudfall-0.2.0/sdk/tests/test_operations_dashboard.py +163 -0
- cloudfall-0.2.0/sdk/tests/test_operator.py +635 -0
- cloudfall-0.2.0/sdk/tests/test_project.py +568 -0
- cloudfall-0.2.0/sdk/tests/test_render_api.py +316 -0
- cloudfall-0.2.0/sdk/tests/test_resources.py +53 -0
- cloudfall-0.2.0/sdk/tests/test_secrets.py +155 -0
- cloudfall-0.2.0/sdk/tests/test_service_evidence.py +92 -0
- cloudfall-0.2.0/sdk/tests/test_validation.py +310 -0
- cloudfall-0.2.0/site/public/apple-touch-icon.png +0 -0
- cloudfall-0.2.0/site/public/favicon-32.png +0 -0
- cloudfall-0.2.0/site/public/favicon.svg +4 -0
- cloudfall-0.2.0/site/public/icon-192.png +0 -0
- cloudfall-0.2.0/site/public/icon-512.png +0 -0
- cloudfall-0.2.0/site/public/index.html +491 -0
- cloudfall-0.2.0/site/public/manifesto.html +367 -0
- cloudfall-0.2.0/site/public/og.png +0 -0
- cloudfall-0.2.0/site/public/site.webmanifest +23 -0
- cloudfall-0.2.0/site/src/worker.js +10 -0
- cloudfall-0.2.0/site/wrangler.jsonc +18 -0
- cloudfall-0.2.0/uv.lock +1198 -0
|
@@ -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).
|