rcf-lite 0.24.0 → 0.26.0
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.
- package/CHANGELOG.md +36 -0
- package/blueprints/application-account-settings/docs/topics.md +4 -1
- package/blueprints/application-admin-console/docs/topics.md +4 -1
- package/blueprints/application-api-rest/docs/topics.md +4 -1
- package/blueprints/application-charts/docs/topics.md +4 -1
- package/blueprints/application-dashboard/docs/topics.md +4 -1
- package/blueprints/application-datatable/docs/topics.md +4 -1
- package/blueprints/application-empty-error-states/docs/topics.md +4 -1
- package/blueprints/application-error-handling/docs/topics.md +4 -1
- package/blueprints/application-file-upload/docs/topics.md +4 -1
- package/blueprints/application-forms-wizard/docs/topics.md +4 -1
- package/blueprints/application-notifications-in-app/docs/topics.md +4 -1
- package/blueprints/application-onboarding-tour/docs/topics.md +4 -1
- package/blueprints/application-spa/docs/topics.md +4 -1
- package/blueprints/delivery-ci-workflows/docs/topics.md +4 -1
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +4 -1
- package/blueprints/deploy-hetzner-server/CHANGELOG.md +55 -0
- package/blueprints/deploy-hetzner-server/README.md +202 -0
- package/blueprints/deploy-hetzner-server/blueprint.json +233 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +14 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +14 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +20 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +14 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/cloud-init-render-lint.mjs +71 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/hcloud-dry-run-mock.mjs +164 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/manifest-schema-validate.mjs +182 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/probe-utils.mjs +112 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs +64 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-snapshot-on-demand.mjs +60 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-throwaway-server-provision.mjs +58 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-cloud-init-render-lint.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-hcloud-dry-run-mock.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-manifest-schema-validate.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-cloud-init-hardened.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-snapshot-on-demand.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-throwaway-server-provision.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-001.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-002.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-003.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-004.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-005.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-006.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/schemas/hetzner-server.schema.json +108 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3801-deploy-hetzner-server-provisioner.json +27 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +26 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +26 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +25 -0
- package/blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl +113 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37101.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37102.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37103.json +25 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37104.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37105.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37106.json +25 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37107.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37108.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37109.json +24 -0
- package/blueprints/deploy-hetzner-server/docs/topics.md +48 -0
- package/blueprints/deploy-hetzner-server/guide/deploy-hetzner-server.md +180 -0
- package/blueprints/edge-cloudflare-access/docs/topics.md +4 -1
- package/blueprints/edge-cloudflare-rate-limiting/docs/topics.md +4 -1
- package/blueprints/edge-cloudflare-tunnel/CHANGELOG.md +23 -0
- package/blueprints/edge-cloudflare-tunnel/README.md +257 -0
- package/blueprints/edge-cloudflare-tunnel/blueprint.json +168 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +23 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +19 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +19 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/aud-presence-check.mjs +114 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/cloudflared-config-lint.mjs +80 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/manifest-schema-validate.mjs +237 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/probe-utils.mjs +231 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-connector-healthy.mjs +143 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-tunnel-hostname-routes.mjs +166 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-aud-presence-check.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-cloudflared-config-lint.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-manifest-schema-validate.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-connector-healthy.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-tunnel-hostname-routes.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/tunnel-manifest.schema.json +55 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-001.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-002.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-003.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-004.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-005.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +26 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +26 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +25 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39101.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39102.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39103.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39104.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39105.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39106.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39107.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39108.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/docs/topics.md +72 -0
- package/blueprints/edge-cloudflare-tunnel/guide/edge-cloudflare-tunnel.md +204 -0
- package/blueprints/edge-cloudflare-turnstile/docs/topics.md +4 -1
- package/blueprints/email-smtp-resend/docs/topics.md +4 -1
- package/blueprints/jobs-background/docs/topics.md +4 -1
- package/blueprints/messaging-queue-cloudflare/CHANGELOG.md +19 -0
- package/blueprints/messaging-queue-cloudflare/README.md +2 -2
- package/blueprints/messaging-queue-cloudflare/blueprint.json +1 -1
- package/blueprints/messaging-queue-cloudflare/contributions/probes/real-account-concurrency-smoke.mjs +192 -37
- package/blueprints/messaging-queue-cloudflare/docs/topics.md +4 -1
- package/blueprints/object-storage-s3/CHANGELOG.md +11 -0
- package/blueprints/object-storage-s3/README.md +14 -1
- package/blueprints/object-storage-s3/blueprint.json +12 -1
- package/blueprints/object-storage-s3/contributions/adrs/adr-2905-object-storage-s3-hetzner-object-storage-provider.json +30 -0
- package/blueprints/object-storage-s3/contributions/probes/hetzner-object-storage-round-trip.mjs +136 -0
- package/blueprints/object-storage-s3/contributions/probes/run-hetzner-object-storage-round-trip.mjs +11 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-101.json +18 -0
- package/blueprints/object-storage-s3/contributions/tacs/tac-2904-object-storage-s3-hetzner-endpoint-helper.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28110.json +25 -0
- package/blueprints/object-storage-s3/docs/topics.md +4 -1
- package/blueprints/object-storage-s3/guide/object-storage-s3.md +36 -0
- package/blueprints/observability-essentials/docs/topics.md +4 -1
- package/blueprints/observability-logging/docs/topics.md +4 -1
- package/blueprints/observability-probe-endpoints/docs/topics.md +4 -1
- package/blueprints/persistence-data-d1/docs/topics.md +4 -1
- package/blueprints/persistence-data-postgres/docs/topics.md +4 -1
- package/blueprints/persistence-data-sqlite/docs/topics.md +4 -1
- package/blueprints/platform-cloudflare-cron-triggers/docs/topics.md +4 -1
- package/blueprints/platform-cloudflare-durable-objects/CHANGELOG.md +15 -0
- package/blueprints/platform-cloudflare-durable-objects/blueprint.json +1 -1
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/namespace-facade-ready.mjs +3 -3
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/probe-utils.mjs +28 -14
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/real-account-storage-smoke.mjs +132 -26
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/sole-reader-scan.mjs +40 -39
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/storage-round-trip.mjs +26 -16
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/websocket-hub-broadcast.mjs +31 -34
- package/blueprints/platform-cloudflare-durable-objects/docs/topics.md +4 -1
- package/blueprints/platform-cloudflare-kv/CHANGELOG.md +40 -1
- package/blueprints/platform-cloudflare-kv/README.md +41 -13
- package/blueprints/platform-cloudflare-kv/blueprint.json +1 -1
- package/blueprints/platform-cloudflare-kv/contributions/probes/cache-aside-hit-then-miss.mjs +26 -26
- package/blueprints/platform-cloudflare-kv/contributions/probes/event-secrecy.mjs +49 -31
- package/blueprints/platform-cloudflare-kv/contributions/probes/facade-round-trip.mjs +58 -16
- package/blueprints/platform-cloudflare-kv/contributions/probes/list-with-prefix.mjs +6 -5
- package/blueprints/platform-cloudflare-kv/contributions/probes/real-account-eventual-consistency-smoke.mjs +131 -67
- package/blueprints/platform-cloudflare-kv/docs/topics.md +4 -1
- package/blueprints/platform-docker-compose-host/CHANGELOG.md +12 -0
- package/blueprints/platform-docker-compose-host/README.md +76 -0
- package/blueprints/platform-docker-compose-host/blueprint.json +209 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3901-platform-docker-compose-host-contract.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3904-platform-docker-compose-host-log-driver.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/caddyfile-validate.mjs +108 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/compose-config-lint.mjs +231 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/probe-utils.mjs +185 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/real-account-minimal-stack-up.mjs +41 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/real-account-reload-burst.mjs +36 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-caddyfile-validate.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-compose-config-lint.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-minimal-stack-up.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-reload-burst.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-secrets-as-files-scan.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/secrets-as-files-scan.mjs +116 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-001.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-002.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-003.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-004.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-005.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-006.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38101.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38102.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38103.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38104.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38105.json +25 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38106.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38107.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38108.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38109.json +24 -0
- package/blueprints/platform-docker-compose-host/docs/topics.md +71 -0
- package/blueprints/platform-docker-compose-host/guide/platform-docker-compose-host.md +94 -0
- package/blueprints/security-auth-clerk/docs/topics.md +4 -1
- package/blueprints/security-auth-keycloak/docs/topics.md +4 -1
- package/blueprints/security-auth-magic-link/docs/topics.md +4 -1
- package/blueprints/security-auth-oauth2/docs/topics.md +4 -1
- package/blueprints/security-secrets-management/docs/topics.md +4 -1
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/harness-template.md +67 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +67 -0
- package/package.json +2 -2
- package/rcf/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +13 -0
- package/rcf/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +13 -0
- package/rcf/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +13 -0
- package/rcf/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +13 -0
- package/rcf/adrs/adr-3901-platform-docker-compose-host-contract.json +13 -0
- package/rcf/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +13 -0
- package/rcf/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +13 -0
- package/rcf/adrs/adr-3904-platform-docker-compose-host-log-driver.json +13 -0
- package/rcf/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +13 -0
- package/rcf/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +13 -0
- package/rcf/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +13 -0
- package/rcf/code-nodes/cn-410.json +20 -0
- package/rcf/code-nodes/cn-440.json +20 -0
- package/rcf/code-nodes/cn-470.json +19 -0
- package/rcf/code-nodes/cn-500.json +12 -0
- package/rcf/code-nodes/cn-510.json +12 -0
- package/rcf/code-nodes/cn-520.json +14 -0
- package/rcf/code-nodes/cn-521.json +12 -0
- package/rcf/code-nodes/cn-522.json +13 -0
- package/rcf/code-nodes/cn-523.json +12 -0
- package/rcf/code-nodes/cn-524.json +14 -0
- package/rcf/code-nodes/cn-525.json +12 -0
- package/rcf/code-nodes/cn-526.json +14 -0
- package/rcf/fbs/fbs-130.json +23 -0
- package/rcf/fbs/fbs-140.json +23 -0
- package/rcf/fbs/fbs-150.json +22 -0
- package/rcf/fbs/fbs-160.json +15 -0
- package/rcf/fbs/fbs-165.json +15 -0
- package/rcf/fbs/fbs-170.json +16 -0
- package/rcf/fbs/fbs-171.json +16 -0
- package/rcf/fbs/fbs-172.json +15 -0
- package/rcf/fbs/fbs-173.json +17 -0
- package/rcf/fbs/fbs-174.json +17 -0
- package/rcf/requirements/req-110.json +58 -0
- package/rcf/requirements/req-111.json +32 -0
- package/rcf/requirements/req-112.json +42 -0
- package/rcf/requirements/req-113.json +33 -0
- package/rcf/requirements/req-114.json +20 -0
- package/rcf/requirements/req-115.json +38 -0
- package/rcf/requirements/req-120.json +20 -0
- package/rcf/requirements/req-121.json +20 -0
- package/rcf/requirements/req-122.json +20 -0
- package/rcf/requirements/req-123.json +42 -0
- package/rcf/requirements/req-124.json +27 -0
- package/rcf/requirements/req-125.json +20 -0
- package/rcf/requirements/req-130.json +32 -0
- package/rcf/requirements/req-131.json +54 -0
- package/rcf/requirements/req-132.json +27 -0
- package/rcf/requirements/req-133.json +20 -0
- package/rcf/requirements/req-134.json +42 -0
- package/rcf/requirements/req-140.json +149 -0
- package/rcf/requirements/req-145.json +53 -0
- package/rcf/requirements/req-150.json +20 -0
- package/rcf/requirements/req-151.json +85 -0
- package/rcf/requirements/req-152.json +20 -0
- package/rcf/requirements/req-153.json +59 -0
- package/rcf/requirements/req-154.json +44 -0
- package/rcf/tacs/tac-3801-deploy-hetzner-server-provisioner.json +17 -0
- package/rcf/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +17 -0
- package/rcf/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +17 -0
- package/rcf/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +16 -0
- package/rcf/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +17 -0
- package/rcf/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +17 -0
- package/rcf/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +17 -0
- package/rcf/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +17 -0
- package/rcf/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +17 -0
- package/rcf/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +17 -0
- package/rcf/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +16 -0
- package/rcf/test-suites/ts-071.json +2 -2
- package/rcf/test-suites/ts-086.json +2 -2
- package/rcf/test-suites/ts-140.json +86 -0
- package/rcf/test-suites/ts-150.json +86 -0
- package/rcf/test-suites/ts-160.json +78 -0
- package/rcf/test-suites/ts-170.json +22 -0
- package/rcf/test-suites/ts-175.json +22 -0
- package/rcf/test-suites/ts-180.json +30 -0
- package/rcf/test-suites/ts-181.json +30 -0
- package/rcf/test-suites/ts-182.json +22 -0
- package/rcf/test-suites/ts-183.json +38 -0
- package/rcf/test-suites/ts-184.json +38 -0
- package/rcf/user-stories/us-11001.json +23 -0
- package/rcf/user-stories/us-11101.json +23 -0
- package/rcf/user-stories/us-11102.json +24 -0
- package/rcf/user-stories/us-11201.json +23 -0
- package/rcf/user-stories/us-11202.json +23 -0
- package/rcf/user-stories/us-11301.json +24 -0
- package/rcf/user-stories/us-11401.json +23 -0
- package/rcf/user-stories/us-11402.json +23 -0
- package/rcf/user-stories/us-11501.json +23 -0
- package/rcf/user-stories/us-12001.json +23 -0
- package/rcf/user-stories/us-12101.json +23 -0
- package/rcf/user-stories/us-12102.json +23 -0
- package/rcf/user-stories/us-12201.json +23 -0
- package/rcf/user-stories/us-12202.json +24 -0
- package/rcf/user-stories/us-12301.json +23 -0
- package/rcf/user-stories/us-12401.json +23 -0
- package/rcf/user-stories/us-12402.json +23 -0
- package/rcf/user-stories/us-12501.json +23 -0
- package/rcf/user-stories/us-13001.json +23 -0
- package/rcf/user-stories/us-13002.json +23 -0
- package/rcf/user-stories/us-13101.json +23 -0
- package/rcf/user-stories/us-13102.json +23 -0
- package/rcf/user-stories/us-13201.json +23 -0
- package/rcf/user-stories/us-13301.json +23 -0
- package/rcf/user-stories/us-13302.json +23 -0
- package/rcf/user-stories/us-13401.json +23 -0
- package/rcf/user-stories/us-14001.json +21 -0
- package/rcf/user-stories/us-14501.json +24 -0
- package/rcf/user-stories/us-15001.json +26 -0
- package/rcf/user-stories/us-15101.json +26 -0
- package/rcf/user-stories/us-15201.json +21 -0
- package/rcf/user-stories/us-15301.json +31 -0
- package/rcf/user-stories/us-15401.json +31 -0
- package/releases/releases.yaml +21 -1
- package/src/blueprint/apply.js +64 -0
- package/src/blueprint/consistency-lint.js +432 -0
- package/src/blueprint/disposition-ledger.js +224 -0
- package/src/blueprint/dispositions.js +44 -0
- package/src/blueprint/index.js +19 -0
- package/src/browser-verify/manifest-writer.js +53 -2
- package/src/browser-verify/pack-runner.js +18 -0
- package/src/cli/blueprint.js +103 -0
- package/src/setup/identity-seed.js +18 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "platform-docker-compose-host-REQ-002",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Every secret is injected via a docker compose secrets file mount; the compose file carries no plaintext secret literal (grep-refuse); references security-secrets-management when applied",
|
|
5
|
+
"description": "Every secret referenced by any compose service is declared under the top-level secrets: key with a file: source, and the service references it through its service-level secrets: array so docker mounts the value at 0o400 inside the container. The compose file contains no plaintext secret literal on any argument, environment entry, or command clause (grep-refuse). When security-secrets-management is applied, the secret source path resolves through that facade; when it is not, the guide documents a file-based fallback path.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "platform",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Ratified in the Hetzner round 7 spec (2026-09-07 section 5.2 REQ-002). Ships as v1.0.0 of platform-docker-compose-host.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:platform-docker-compose-host",
|
|
12
|
+
"capability:containerHost"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "platform-docker-compose-host-REQ-003",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Every HTTP-terminating compose service declares a healthcheck; docker compose config refuses on missing",
|
|
5
|
+
"description": "Every service under services: whose image terminates HTTP declares a healthcheck: block. The compose-config-lint refuses at docker compose config time on any HTTP-terminating service missing the block, naming the service. Healthcheck defaults per service kind are elicited (http default, tcp and command alternatives). The stack is not considered up until every declared service reaches healthy inside the elicited timeout.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "platform",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Ratified in the Hetzner round 7 spec (2026-09-07 section 5.2 REQ-003). Ships as v1.0.0 of platform-docker-compose-host.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:platform-docker-compose-host",
|
|
12
|
+
"capability:containerHost"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "platform-docker-compose-host-REQ-004",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Restart discipline enforced at lint time: unless-stopped on long-running services, on-failure on jobs; unclassified value refuses",
|
|
5
|
+
"description": "Every service declares a restart policy. Long-running services declare restart: unless-stopped. Jobs (short-lived services that exit after completion) declare restart: on-failure. Any service that declares neither, or declares a value outside the two allowed policies, refuses at the compose-config-lint gate before docker compose up runs. The lint names the service and the offending value.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "platform",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Ratified in the Hetzner round 7 spec (2026-09-07 section 5.2 REQ-004). Ships as v1.0.0 of platform-docker-compose-host.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:platform-docker-compose-host",
|
|
12
|
+
"capability:containerHost"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "platform-docker-compose-host-REQ-005",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Zero-downtime reverse-proxy reload via bind-mounted config artefact; verb per proxy; no non-2xx or dropped connection inside the elicited window",
|
|
5
|
+
"description": "The reverse-proxy config file (caddy/Caddyfile when reverseProxy=caddy, traefik/traefik.yaml when reverseProxy=traefik) is a first-class checked-in artefact. The compose runtime bind-mounts the artefact read-only into the proxy service. The reload verb is docker compose exec caddy caddy reload for Caddy, or the documented Traefik equivalent. During the elicited reload window (10 s default) a client burst against the proxy sees no non-2xx and no dropped connection.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "platform",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Ratified in the Hetzner round 7 spec (2026-09-07 section 5.2 REQ-005). Ships as v1.0.0 of platform-docker-compose-host.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:platform-docker-compose-host",
|
|
12
|
+
"capability:containerHost"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "platform-docker-compose-host-REQ-006",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Log shipping to the elicited observability-logging sink: journald default matching Debian 12 and Ubuntu 24.04 baseline; loki opt-in when the project runs Grafana",
|
|
5
|
+
"description": "Every container writes stdout and stderr through the elicited log driver. Journald is the shipped default matching the Debian 12 and Ubuntu 24.04 baselines T-1 cloud-init sets up. Loki is the opt-in alternative when the applying project runs Grafana. When observability-logging is applied, the compose file wires the elicited driver to that companion. The compose-config-lint asserts every service declares a logging driver whose value is one of journald or loki.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "platform",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Ratified in the Hetzner round 7 spec (2026-09-07 section 5.2 REQ-006). Ships as v1.0.0 of platform-docker-compose-host.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:platform-docker-compose-host",
|
|
12
|
+
"capability:containerHost"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3901-platform-docker-compose-host-compose-layout",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Platform-docker-compose-host compose layout",
|
|
8
|
+
"purpose": "The applying project ships one compose.yaml at the repo root whose services key names every container the stack runs. Every entry is one image running one container; networks and volumes are top-level named entries; a colocated .env carries non-secret configuration values (never inline). The container host reads compose.yaml as the runtime topology source and refuses to bring the stack up on layout drift.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Serve compose.yaml at the repo root as the sole topology surface (AC-composeHost-topologyValid, AC-composeHost-upClean).",
|
|
11
|
+
"Guarantee service-per-container: each entry under services: runs one image (AC-composeHost-topologyValid).",
|
|
12
|
+
"Declare all networks and volumes as top-level named entries: no bare defaults (AC-composeHost-topologyValid).",
|
|
13
|
+
"Bind non-secret configuration through a colocated .env: no inline literals (AC-composeHost-topologyValid)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "One module exporting a compose.yaml discovery helper that resolves the applied fixture root and returns the parsed document; consumers scan services/networks/volumes/env-file/logging by walking the parsed document.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "default export",
|
|
19
|
+
"kind": "facade",
|
|
20
|
+
"summary": "Compose stack scan facade exported from the applied fixture module."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"dependencies": [],
|
|
24
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
25
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3902-platform-docker-compose-host-secrets-mount",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Platform-docker-compose-host secrets mount",
|
|
8
|
+
"purpose": "The compose file declares each secret under its top-level secrets: key with a file: source. Services reference secrets through their service-level secrets: array so docker mounts them as files at 0o400 inside the container. When security-secrets-management is applied, the source path resolves through that facade; when it is not, the guide names a file-based fallback. No plaintext secret literal appears anywhere in compose.yaml or a referenced service config (grep-refuse).",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Declare every secret through a compose secrets: file mount (AC-composeHost-secretShape, AC-composeHost-secretsAreFiles).",
|
|
11
|
+
"Refuse a plaintext secret literal anywhere in compose.yaml (AC-composeHost-secretsAreFiles).",
|
|
12
|
+
"Mount each secret file at 0o400 inside the container (AC-composeHost-secretShape).",
|
|
13
|
+
"Bind to security-secrets-management when applied; document the file-based fallback (AC-composeHost-secretShape)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "The secrets-as-files-scan probe module walks the parsed compose document plus the referenced service config files and asserts the secret shape; the mutation switch drives the failure path.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "default export",
|
|
19
|
+
"kind": "facade",
|
|
20
|
+
"summary": "Compose stack scan facade exported from the applied fixture module."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"dependencies": [],
|
|
24
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
25
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3903-platform-docker-compose-host-healthcheck-lint",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Platform-docker-compose-host healthcheck and lifecycle lint",
|
|
8
|
+
"purpose": "A lint pass runs before docker compose up. It asserts every HTTP-terminating service declares a healthcheck: block; every service declares a restart: policy whose value is one of unless-stopped or on-failure; every service declares a logging: driver: entry whose value is one of journald or loki. The lint refuses on any missing or unclassified line, naming the service and the offending field.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Assert every HTTP-terminating service declares a healthcheck: block (AC-composeHost-healthcheckLint).",
|
|
11
|
+
"Assert every service declares a restart policy in the allowed set (AC-composeHost-restartClassification).",
|
|
12
|
+
"Assert every service declares a logging driver in the allowed set (AC-composeHost-logDriverClassification).",
|
|
13
|
+
"Refuse on missing or unclassified fields, naming the service (AC-composeHost-healthcheckLint, AC-composeHost-restartClassification, AC-composeHost-logDriverClassification)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "The compose-config-lint probe module orchestrates docker compose config, healthcheck presence, restart classification and logging driver classification through one parsed compose document; mutation switches drive the four failure paths.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "default export",
|
|
19
|
+
"kind": "facade",
|
|
20
|
+
"summary": "Compose stack scan facade exported from the applied fixture module."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"dependencies": [],
|
|
24
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
25
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3904-platform-docker-compose-host-reverse-proxy-artefact",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Platform-docker-compose-host reverse-proxy artefact",
|
|
8
|
+
"purpose": "The reverse-proxy config file (caddy/Caddyfile when reverseProxy=caddy, traefik/traefik.yaml when reverseProxy=traefik) lives as a first-class checked-in artefact under the applying repo. The compose file bind-mounts the artefact read-only into the proxy service. The gate runs the vendor validator against the artefact and refuses on an invalid directive. Under the reload verb (docker compose exec caddy caddy reload or the Traefik equivalent), a burst against the proxy inside the elicited window sees no non-2xx and no dropped connection.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Ship the reverse-proxy config as a checked-in artefact bind-mounted into the proxy (AC-composeHost-reverseProxyArtefactValid).",
|
|
11
|
+
"Validate the artefact with the vendor validator at gate time (AC-composeHost-reverseProxyArtefactValid).",
|
|
12
|
+
"Prove zero-downtime reload with a real-account burst (AC-composeHost-zeroDowntimeReload).",
|
|
13
|
+
"Name the reload verb per proxy in the guide and the fixture README (AC-composeHost-reverseProxyArtefactValid, AC-composeHost-zeroDowntimeReload)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "The caddyfile-validate probe shells to caddy validate (local binary or caddy:2 container image) with SIMULATE_INVALID_CADDYFILE writing a broken directive into a scratch copy; the real-account-reload-burst probe drives an undici burst on the throwaway server while docker compose exec caddy caddy reload runs.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "default export",
|
|
19
|
+
"kind": "facade",
|
|
20
|
+
"summary": "Compose stack scan facade exported from the applied fixture module."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"dependencies": [],
|
|
24
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
25
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38101",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "compose.yaml at repo root is the single-source runtime topology (service-per-container, named networks and volumes, .env for non-secret config)",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint on a project that runs its stack on a Hetzner cloudHost",
|
|
9
|
+
"iWant": "a single compose.yaml at the repo root to declare every service as one container with explicit named networks, named volumes, and non-secret config in a .env file",
|
|
10
|
+
"soThat": "the runtime topology has one auditable seam and the container host can bring the stack up from the file alone",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-topologyValid",
|
|
14
|
+
"description": "The applied compose.yaml at the repo root declares (a) every entry under services: as exactly one image running one container, (b) at least one named network under top-level networks:, (c) at least one named volume under top-level volumes:, and (d) non-secret configuration values are loaded from a colocated .env file rather than inlined as service environment entries.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3901-platform-docker-compose-host-compose-layout"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38102",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-002",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "secrets flow via docker compose secrets file mounts, no plaintext literal in compose.yaml",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint on a project with secret material bound to a container",
|
|
9
|
+
"iWant": "every secret to be declared under the compose secrets top-level key and mounted into the service via secrets: rather than inlined as an environment literal",
|
|
10
|
+
"soThat": "the compose file is safe to read at review time and the mounted file inherits 0o400 inside the container",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-secretShape",
|
|
14
|
+
"description": "The applied compose.yaml declares at least one secret under the top-level secrets: key with a file: source and references it from the consuming service through the service-level secrets: array. The mounted secret file lands at mode 0o400 inside the container.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3902-platform-docker-compose-host-secrets-mount"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38103",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-002",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "the secrets-as-files probe refuses any plaintext secret literal in compose.yaml or a referenced service config",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint on a project with sensitive material",
|
|
9
|
+
"iWant": "the secrets-as-files probe to walk compose.yaml and every referenced service config in the fixture and refuse on any plaintext secret literal, naming the file and line",
|
|
10
|
+
"soThat": "a secret accidentally inlined in a review is caught by the probe before docker compose up runs",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-secretsAreFiles",
|
|
14
|
+
"description": "The secrets-as-files-scan probe walks compose.yaml plus every referenced service config file in the applied fixture and asserts (a) no line contains the applied plaintext token pattern (literal name plus literal value), (b) every declared secret in the compose secrets: key has a file: source, and (c) SIMULATE_PLAINTEXT_SECRET=true writes a plaintext literal into compose.yaml and the probe FAILS naming the file and the literal.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3902-platform-docker-compose-host-secrets-mount"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38104",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-003",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "healthcheck contract enforced by docker compose config: refuses on any HTTP-terminating service missing a healthcheck block",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint on a project that terminates HTTP inside a container",
|
|
9
|
+
"iWant": "the compose-config-lint to refuse at gate time on any HTTP-terminating service that is missing a healthcheck block, naming the service",
|
|
10
|
+
"soThat": "the stack never comes up with a service the container host cannot know is ready",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-healthcheckLint",
|
|
14
|
+
"description": "The compose-config-lint probe shells to docker compose config against the applied compose.yaml, asserts exit 0, and additionally asserts every HTTP-terminating service declares a healthcheck: block. SIMULATE_MISSING_HEALTHCHECK=true strips the block from the web service and the probe FAILS naming the service.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3903-platform-docker-compose-host-healthcheck-lint"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38105",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-003",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "the minimal stack comes up clean and every declared service reaches healthy on the throwaway server",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint on a Hetzner cloudHost",
|
|
9
|
+
"iWant": "the real-account minimal-stack-up probe to scp the compose.yaml to the throwaway server, run docker compose up -d --wait, and assert every declared service reaches healthy inside the elicited timeout",
|
|
10
|
+
"soThat": "the composeStackReady claim is proven on real docker before the blueprint ships",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-upClean",
|
|
14
|
+
"description": "The real-account-minimal-stack-up probe, when CI_HAS_HETZNER_ACCOUNT is set, scps the compose.yaml to the throwaway server, runs docker compose up -d --wait, asserts every declared service reaches healthy inside the elicited timeout, and tears the stack plus the server down in always(). Without CI_HAS_HETZNER_ACCOUNT the probe records accountBoundSkipped: true and the aggregate flips to pass.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3901-platform-docker-compose-host-compose-layout",
|
|
21
|
+
"TAC-3903-platform-docker-compose-host-healthcheck-lint"
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38106",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-004",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "restart discipline enforced at lint: unless-stopped or on-failure only",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint",
|
|
9
|
+
"iWant": "the compose-config-lint to enforce a restart policy on every service and refuse on any value outside the two allowed policies (unless-stopped for long-running, on-failure for jobs)",
|
|
10
|
+
"soThat": "the operator sees a wrong or missing restart policy at gate time rather than as a stuck container",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-restartClassification",
|
|
14
|
+
"description": "The compose-config-lint probe asserts every service in the compose.yaml declares a restart policy AND every declared value is one of unless-stopped or on-failure. SIMULATE_UNCLASSIFIED_RESTART=true sets restart: always on the web service and the probe FAILS naming the service and the disallowed value.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3903-platform-docker-compose-host-healthcheck-lint"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38107",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-005",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "reverse-proxy config is a first-class versioned artefact validated by the vendor validator",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint with reverseProxy=caddy",
|
|
9
|
+
"iWant": "a checked-in caddy/Caddyfile bind-mounted into the proxy service so the reload verb picks up an operator change, validated at gate time by caddy validate",
|
|
10
|
+
"soThat": "a broken Caddyfile is caught before the reload burst runs, and there is one place in the tree the operator edits to route hostnames",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-reverseProxyArtefactValid",
|
|
14
|
+
"description": "The applied fixture ships caddy/Caddyfile under a caddy/ directory, the compose file bind-mounts it read-only into the caddy service, and the caddyfile-validate probe shells to caddy validate against the file (using the caddy:2 container image when a local caddy binary is not on PATH) and asserts exit 0. SIMULATE_INVALID_CADDYFILE=true writes an invalid directive and the probe FAILS naming the line. Skipped when the applied reverseProxy is not caddy.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3904-platform-docker-compose-host-reverse-proxy-artefact"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38108",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-005",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "zero-downtime reverse-proxy reload: a client burst inside the elicited window sees no non-2xx and no dropped connection",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint",
|
|
9
|
+
"iWant": "the real-account reload-burst probe to fire an undici burst against the reverse proxy while docker compose exec caddy caddy reload runs and assert no non-2xx or dropped connection inside the elicited window",
|
|
10
|
+
"soThat": "the zeroDowntimeReload claim is proven on real docker before the blueprint ships",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-zeroDowntimeReload",
|
|
14
|
+
"description": "The real-account-reload-burst probe, when CI_HAS_HETZNER_ACCOUNT is set, runs undici GETs against the proxy service on the throwaway server while docker compose exec caddy caddy reload runs, asserts every request returns a 2xx status inside the elicited window (10 s default), asserts no dropped connection, and tears the stack down in always(). Without CI_HAS_HETZNER_ACCOUNT the probe records accountBoundSkipped: true and the aggregate flips to pass.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3904-platform-docker-compose-host-reverse-proxy-artefact"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "platform-docker-compose-host-US-38109",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "platform-docker-compose-host-REQ-006",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "log shipping to the elicited observability-logging sink: every service declares a logging driver in the allowed set",
|
|
8
|
+
"asA": "shelf consumer applying the platform-docker-compose-host v1.0.0 blueprint on a project that may run Grafana or the estate default",
|
|
9
|
+
"iWant": "the elicited log driver to be wired into every service in the compose file (journald default, loki opt-in) and the choice recorded on the log-driver ADR",
|
|
10
|
+
"soThat": "container stdout and stderr reach the applied observability sink without a per-service edit",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-composeHost-logDriverClassification",
|
|
14
|
+
"description": "Every service in the applied compose.yaml declares a logging: driver: block whose driver value matches the elicited log driver (default journald). The compose-config-lint probe asserts (a) every service declares logging: driver, (b) the value is one of journald or loki, and (c) SIMULATE_UNCLASSIFIED_LOG_DRIVER=true sets a disallowed driver and the probe FAILS naming the service and the disallowed value.",
|
|
15
|
+
"testable": true,
|
|
16
|
+
"scope": "runtime"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"tacIds": [
|
|
20
|
+
"TAC-3903-platform-docker-compose-host-healthcheck-lint"
|
|
21
|
+
],
|
|
22
|
+
"createdAt": "2026-09-08T00:00:00.000Z",
|
|
23
|
+
"updatedAt": "2026-09-08T00:00:00.000Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# platform-docker-compose-host blueprint coordination vocabulary
|
|
2
|
+
|
|
3
|
+
This file is the platform-docker-compose-host half of the cross-blueprint contract. The Phase 1 conflict detector matches scope:global ADR topics by EXACT string equality, and AC ids are unnamespaced by the 0.4.4 grammar. Any blueprint intended to compose with this one must reuse these exact strings and respect these bands.
|
|
4
|
+
|
|
5
|
+
## Global ADR topics this blueprint contributes (exact strings)
|
|
6
|
+
|
|
7
|
+
| Topic string | platform-docker-compose-host contribution | Origin | Composition note |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `containerHostContract` | ADR-3901-platform-docker-compose-host-contract | Minted by this blueprint. Names the wire-shape contract for the project-wide container-orchestration surface running on a `cloudHost`; the shipped answer is docker compose as source of truth (compose.yaml at repo root; healthchecks required; secrets as file mounts; restart discipline unless-stopped or on-failure; reverse-proxy config as a first-class checked-in artefact) per `https://docs.docker.com/reference/compose-file/` and `https://docs.docker.com/engine/install/ubuntu/`. | The one project-wide decision on how services are declared, wired and brought up on a container-orchestration surface. A future non-compose sibling (Docker Swarm, Nomad) would contribute the same topic string with a different mechanism, forcing a DELIBERATE conflict the operator resolves with a project-level ADR. |
|
|
10
|
+
|
|
11
|
+
The platform-docker-compose-host blueprint claims one global topic (minted here, shipped as the docker compose file contract on a Linux `cloudHost`). ADR-3902 reverse-proxy elicit, ADR-3903 reject Coolify and ADR-3904 log-driver elicit do not contribute global topics; a composing blueprint that holds an opinion on either authors its own project-level ADR.
|
|
12
|
+
|
|
13
|
+
Rules for new topics: lower camel case, one concept per topic, no version suffixes. A topic names the decision area, not the chosen answer.
|
|
14
|
+
|
|
15
|
+
## Id number bands (registry bootstrap)
|
|
16
|
+
|
|
17
|
+
This table is maintained shelf-wide across every blueprint's `docs/topics.md`. Rows are recorded at ship, never predicted.
|
|
18
|
+
|
|
19
|
+
| Blueprint | US band | ADR/TAC suffix block | Status | Global topics |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| application-spa | 1101-1899 | 2xx | shipped v1.3.0 | `clientRouting`, `theming`, `clientState`, `errorEnvelope`, `authModel` |
|
|
22
|
+
| application-api-rest | 2101-2899 | 3xx | shipped v1.0.0 | `errorEnvelope`, `authModel`, `apiVersioning`, `logging` |
|
|
23
|
+
| security-auth-magic-link | 3101-3899 | 5xx | shipped v1.2.0 | `authModel` |
|
|
24
|
+
| email-smtp-resend | 4101-4899 | 4xx | shipped v1.0.0 | none |
|
|
25
|
+
| hello-panel (walkthrough exemplar) | 4101-4899 | 4xx | doc-reserved; teaching exemplar in `packages/rcf-lite/docs/blueprint-authoring-walkthrough.md`, not shipped as a blueprint directory | `operatorPanel` |
|
|
26
|
+
| persistence-data-sqlite | 5101-5899 | 6xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
27
|
+
| delivery-ci-workflows | 6101-6899 | 7xx | shipped v2.0.0 (renamed from ci-pipeline) | `ciGates`, `strictCoverageGate`, `releaseArtefacts` |
|
|
28
|
+
| observability-essentials | 7101-7899 | 8xx | shipped v2.0.0 | `statusPageContract` |
|
|
29
|
+
| security-secrets-management | 8101-8899 | 9xx | shipped v1.0.1 | `secretsSource` |
|
|
30
|
+
| security-auth-clerk | 9101-9899 | 10xx | shipped v1.0.0 | `authModel` |
|
|
31
|
+
| security-auth-oauth2 | 10101-10899 | 11xx | shipped v1.0.0 | `authModel` |
|
|
32
|
+
| security-auth-keycloak | 11101-11899 | 12xx | shipped v1.0.0 | `authModel` |
|
|
33
|
+
| deploy-cloudflare-workers | 12101-12899 | 13xx | shipped v1.2.0 | `deploymentTarget` |
|
|
34
|
+
| persistence-data-d1 | 13101-13899 | 14xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
35
|
+
| observability-probe-endpoints | 14101-14899 | 15xx | shipped v1.1.0 | `healthProbes`, `readinessSemantics` |
|
|
36
|
+
| observability-logging | 15101-15899 | 16xx | shipped v1.0.0 | `logging` |
|
|
37
|
+
| application-error-handling | 16101-16899 | 17xx | shipped v1.0.0 | `errorHandling` |
|
|
38
|
+
| application-datatable | 17101-17899 | 18xx | shipped v1.0.0 | none |
|
|
39
|
+
| application-charts | 18101-18899 | 19xx | shipped v1.0.0 | none |
|
|
40
|
+
| application-dashboard | 19101-19899 | 20xx | shipped v1.0.0 | none |
|
|
41
|
+
| application-notifications-in-app | 20101-20899 | 21xx | shipped v1.0.0 | none |
|
|
42
|
+
| application-admin-console | 21101-21899 | 22xx | shipped v1.1.0 | `adminConsoleSignInSurface` |
|
|
43
|
+
| application-empty-error-states | 22101-22899 | 23xx | shipped v1.0.0 | none |
|
|
44
|
+
| application-file-upload | 23101-23899 | 24xx | shipped v1.0.0 | none |
|
|
45
|
+
| application-forms-wizard | 24101-24899 | 25xx | shipped v1.0.0 | none |
|
|
46
|
+
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
47
|
+
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
48
|
+
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
49
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
50
|
+
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
51
|
+
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
52
|
+
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
53
|
+
| platform-cloudflare-cron-triggers | 32101-32899 | 33xx | shipped v1.0.0 | `scheduledTriggerContract` |
|
|
54
|
+
| platform-cloudflare-durable-objects | 33101-33899 | 34xx | shipped v1.0.0 | `strongConsistencyCellContract`, `websocketHubContract` |
|
|
55
|
+
| edge-cloudflare-access | 34101-34899 | 35xx | shipped v1.0.0 | `edgeAuthenticationGate` |
|
|
56
|
+
| edge-cloudflare-turnstile | 35101-35899 | 36xx | shipped v1.0.0 | `humanVerificationGate` |
|
|
57
|
+
| edge-cloudflare-rate-limiting | 36101-36899 | 37xx | shipped v1.0.0 | `edgeThrottleContract` |
|
|
58
|
+
| deploy-hetzner-server | 37101-37899 | 38xx | shipped v1.0.0 | `linuxCloudHostContract`, `snapshotAndBackupCadence` |
|
|
59
|
+
| platform-docker-compose-host | 38101-38899 | 39xx | shipped v1.0.0 | `containerHostContract` |
|
|
60
|
+
| edge-cloudflare-tunnel | 39101-39899 | 40xx | shipped v1.0.0 | `edgeIngressBridge` |
|
|
61
|
+
|
|
62
|
+
US 38101-38109 sit at the LOW end of the 38101-38899 band on purpose (watchpost run-4 lesson). A project-side story mechanically derived from a platform-docker-compose-host REQ id into the number 38109 would collide against the shipped US-38109; band headroom (38110-38899) leaves that space.
|
|
63
|
+
|
|
64
|
+
Platform-docker-compose-host suffixes for this blueprint use the 3901-3904 block, continuing the widening pattern (deploy-hetzner-server 3801-3804 for the T-1 sibling).
|
|
65
|
+
|
|
66
|
+
## Cross-references
|
|
67
|
+
|
|
68
|
+
- `deploy-hetzner-server` (v1.0.0): consumed via `capabilities: [cloudHost]`; this blueprint composes on the Hetzner cloudHost T-1 provisions.
|
|
69
|
+
- `security-secrets-management` (v1.0.1): the compose secrets file source resolves through the applied secrets facade when the secrets blueprint is applied; the guide names a file-based fallback path when it is not.
|
|
70
|
+
- Round-7 T-3 `edge-cloudflare-tunnel`: composes on `capabilities: [containerHost]`; runs cloudflared as a compose service in this stack.
|
|
71
|
+
- `observability-logging`: elicited log-driver wires the applied logging companion's endpoint (journald by default, loki opt-in).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Guide: platform-docker-compose-host v1.0.0
|
|
2
|
+
|
|
3
|
+
Round-7 T-2 of the Hetzner spec at `projects/blueprint-library/specs/hetzner-round-7-spec-2026-09-07.md`. Applies on top of `deploy-hetzner-server` (or any future `cloudHost` provider) and mints `containerHost` for downstream consumers.
|
|
4
|
+
|
|
5
|
+
## What this blueprint gives you
|
|
6
|
+
|
|
7
|
+
A docker compose stack as your application runtime on a Linux VM. The compose file is the source of truth for the topology: services, networks, volumes, secrets, healthchecks, restart policy, log driver and the reverse-proxy bind-mount all live in one file at the repo root. Every claim the blueprint makes is provable at gate time by a Node-only probe against a real docker daemon and a real reverse-proxy config.
|
|
8
|
+
|
|
9
|
+
## Layout
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
<repo-root>/
|
|
13
|
+
compose.yaml # single-source topology per REQ-001
|
|
14
|
+
.env # non-secret config per REQ-001
|
|
15
|
+
caddy/
|
|
16
|
+
Caddyfile # reverse-proxy artefact per REQ-005 (when reverseProxy=caddy)
|
|
17
|
+
secrets/
|
|
18
|
+
<name> # file-mount source per REQ-002 (0o400 inside the container)
|
|
19
|
+
src/
|
|
20
|
+
... # your service code, bind-mounted into services
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## When to reach for Caddy vs Traefik vs none
|
|
24
|
+
|
|
25
|
+
The `reverse-proxy` elicit is the one you will hit first. The defaults:
|
|
26
|
+
|
|
27
|
+
- **`caddy` (default per Baz decision 19).** Matches the estate baseline (dev-01 and ops-01 both run Caddy). Small config file, automatic HTTPS in production, `docker compose exec caddy caddy reload` picks up changes instantly. Reach for it whenever you do not have a strong project-side reason for something else. See the Caddy docs at https://caddyserver.com/docs/.
|
|
28
|
+
- **`traefik` (alternative).** Reach for it when your project wants docker-label auto-discovery (each service declares its hostname/route via labels in `compose.yaml` and Traefik configures itself). Trade-off: your routing is spread across many service blocks rather than one Caddyfile. See the Traefik docs at https://doc.traefik.io/traefik/.
|
|
29
|
+
- **`none` (alternative).** Reach for it when the project is fronted only by the round-7 tunnel (`edge-cloudflare-tunnel` v1.0.0). The connector terminates at the CF edge and forwards to a service on the internal `web-net` network; no reverse proxy is needed inside compose. The lint refuses to add a `caddy/Caddyfile` when `reverse-proxy` is `none`.
|
|
30
|
+
|
|
31
|
+
The Coolify note (ADR-3903 body): "reject; it shadows what the blueprints contract and adds its own DB and UI as a second source of state". Baz decision 20, verbatim. A `platform-coolify-host` sibling mints on operator demand later; it is not part of round 7.
|
|
32
|
+
|
|
33
|
+
## Healthcheck contract
|
|
34
|
+
|
|
35
|
+
Every HTTP-terminating service declares a `healthcheck:` block. The compose-config-lint probe refuses at `docker compose config` on any missing block, naming the service. Defaults per service kind are elicited:
|
|
36
|
+
|
|
37
|
+
- `http` (default): a `wget --quiet --spider` against the service's `/health` (or `/live`) path.
|
|
38
|
+
- `tcp`: a socket connect against the declared port.
|
|
39
|
+
- `command`: a bespoke test string the operator supplies.
|
|
40
|
+
|
|
41
|
+
The stack is not considered up until every declared service reaches healthy inside the elicited timeout; the real-account minimal-stack-up probe proves this on the throwaway server.
|
|
42
|
+
|
|
43
|
+
## Secrets as files, never literals
|
|
44
|
+
|
|
45
|
+
Under REQ-002 every secret referenced by any compose service is declared under the top-level `secrets:` key with a `file:` source. Services reference the secret through their service-level `secrets:` array so docker mounts the value at 0o400 inside the container at `/run/secrets/<name>`. The compose file carries no plaintext secret literal (grep-refuse).
|
|
46
|
+
|
|
47
|
+
When `security-secrets-management` is applied, the source path resolves through that facade. When it is not, the file-based fallback path is: land the secret in `secrets/<name>` on the applying repo under a `.gitignore` line that lists the file, and rotate through the operator's out-of-band vault. Never commit the secret; never inline the value in `compose.yaml` or `.env`.
|
|
48
|
+
|
|
49
|
+
## When to reach for `journald` vs `loki`
|
|
50
|
+
|
|
51
|
+
The `log-driver` elicit (ADR-3904):
|
|
52
|
+
|
|
53
|
+
- **`journald` (default).** Matches the Debian 12 and Ubuntu 24.04 baseline the T-1 cloud-init sets up on the throwaway server. `journalctl -u docker.service` and `journalctl CONTAINER_NAME=<name>` reach the same stream you would see in `docker logs`.
|
|
54
|
+
- **`loki` (opt-in).** Reach for it when the applying project runs Grafana. The compose logging block wires each service through the Grafana Loki docker plugin; the observability-logging companion supplies the loki endpoint.
|
|
55
|
+
|
|
56
|
+
The compose-config-lint refuses on any driver value outside the two. The lint fires before `docker compose up` runs.
|
|
57
|
+
|
|
58
|
+
## Restart discipline
|
|
59
|
+
|
|
60
|
+
Under REQ-004: `restart: unless-stopped` on long-running services, `restart: on-failure` on jobs (short-lived services that exit after completion). Any service that declares neither, or declares a value outside those two, refuses at the lint. `SIMULATE_UNCLASSIFIED_RESTART=true` on the compose-config-lint probe exercises the refusal path.
|
|
61
|
+
|
|
62
|
+
## Zero-downtime reverse-proxy reload
|
|
63
|
+
|
|
64
|
+
Under REQ-005 the reverse-proxy config lives as a first-class checked-in artefact under `caddy/Caddyfile` (or `traefik/traefik.yaml`). The compose file bind-mounts it read-only into the proxy service. The reload verb is:
|
|
65
|
+
|
|
66
|
+
- `docker compose exec caddy caddy reload` for Caddy.
|
|
67
|
+
- `docker compose kill -s USR1 traefik` for Traefik (the documented equivalent).
|
|
68
|
+
|
|
69
|
+
During the elicited reload window (10 s default per `reload-window-seconds`) a client burst against the proxy sees no non-2xx and no dropped connection. The real-account reload-burst probe proves this on the throwaway server.
|
|
70
|
+
|
|
71
|
+
## Running the probes
|
|
72
|
+
|
|
73
|
+
From the shared throwaway-server fixture directory the three local probes run without a Hetzner account:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
cd packages/rcf-lite/test/fixtures/hetzner-throwaway-server
|
|
77
|
+
node ./run-compose-config-lint.mjs
|
|
78
|
+
node ./run-secrets-as-files-scan.mjs
|
|
79
|
+
node ./run-caddyfile-validate.mjs
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`caddyfile-validate` reaches for a local `caddy` binary when one is on PATH; otherwise it runs `caddy validate` inside the `caddy:2` container via `docker run --rm`. Verify the image once with `docker run --rm caddy:2 caddy version`.
|
|
83
|
+
|
|
84
|
+
Real-account probes require `CI_HAS_HETZNER_ACCOUNT=true` plus `HCLOUD_TOKEN`; without them each records `accountBoundSkipped: true` and the aggregate flips to `pass` per spec section 3.5.
|
|
85
|
+
|
|
86
|
+
## Applying to your project
|
|
87
|
+
|
|
88
|
+
Once T-1 (`deploy-hetzner-server`) has provisioned a `cloudHost`:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
rcf apply platform-docker-compose-host
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Answer the elicits (reverse-proxy, healthcheck-default-kind, log-driver, reload-window-seconds). The blueprint drops `compose.yaml`, `.env`, `caddy/Caddyfile` (when reverseProxy=caddy), the `secrets/` directory (empty, `.gitignored`) and the READMEs into your applying tree. Then `docker compose up -d --wait` on the applied host brings the stack up.
|