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.
Files changed (313) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/blueprints/application-account-settings/docs/topics.md +4 -1
  3. package/blueprints/application-admin-console/docs/topics.md +4 -1
  4. package/blueprints/application-api-rest/docs/topics.md +4 -1
  5. package/blueprints/application-charts/docs/topics.md +4 -1
  6. package/blueprints/application-dashboard/docs/topics.md +4 -1
  7. package/blueprints/application-datatable/docs/topics.md +4 -1
  8. package/blueprints/application-empty-error-states/docs/topics.md +4 -1
  9. package/blueprints/application-error-handling/docs/topics.md +4 -1
  10. package/blueprints/application-file-upload/docs/topics.md +4 -1
  11. package/blueprints/application-forms-wizard/docs/topics.md +4 -1
  12. package/blueprints/application-notifications-in-app/docs/topics.md +4 -1
  13. package/blueprints/application-onboarding-tour/docs/topics.md +4 -1
  14. package/blueprints/application-spa/docs/topics.md +4 -1
  15. package/blueprints/delivery-ci-workflows/docs/topics.md +4 -1
  16. package/blueprints/deploy-cloudflare-workers/docs/topics.md +4 -1
  17. package/blueprints/deploy-hetzner-server/CHANGELOG.md +55 -0
  18. package/blueprints/deploy-hetzner-server/README.md +202 -0
  19. package/blueprints/deploy-hetzner-server/blueprint.json +233 -0
  20. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +14 -0
  21. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +14 -0
  22. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +20 -0
  23. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +14 -0
  24. package/blueprints/deploy-hetzner-server/contributions/probes/cloud-init-render-lint.mjs +71 -0
  25. package/blueprints/deploy-hetzner-server/contributions/probes/hcloud-dry-run-mock.mjs +164 -0
  26. package/blueprints/deploy-hetzner-server/contributions/probes/manifest-schema-validate.mjs +182 -0
  27. package/blueprints/deploy-hetzner-server/contributions/probes/probe-utils.mjs +112 -0
  28. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs +64 -0
  29. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-snapshot-on-demand.mjs +60 -0
  30. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-throwaway-server-provision.mjs +58 -0
  31. package/blueprints/deploy-hetzner-server/contributions/probes/run-cloud-init-render-lint.mjs +9 -0
  32. package/blueprints/deploy-hetzner-server/contributions/probes/run-hcloud-dry-run-mock.mjs +9 -0
  33. package/blueprints/deploy-hetzner-server/contributions/probes/run-manifest-schema-validate.mjs +9 -0
  34. package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-cloud-init-hardened.mjs +9 -0
  35. package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-snapshot-on-demand.mjs +9 -0
  36. package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-throwaway-server-provision.mjs +9 -0
  37. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-001.json +18 -0
  38. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-002.json +18 -0
  39. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-003.json +18 -0
  40. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-004.json +18 -0
  41. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-005.json +18 -0
  42. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-006.json +18 -0
  43. package/blueprints/deploy-hetzner-server/contributions/schemas/hetzner-server.schema.json +108 -0
  44. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3801-deploy-hetzner-server-provisioner.json +27 -0
  45. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +26 -0
  46. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +26 -0
  47. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +25 -0
  48. package/blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl +113 -0
  49. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37101.json +24 -0
  50. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37102.json +24 -0
  51. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37103.json +25 -0
  52. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37104.json +24 -0
  53. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37105.json +24 -0
  54. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37106.json +25 -0
  55. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37107.json +24 -0
  56. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37108.json +24 -0
  57. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37109.json +24 -0
  58. package/blueprints/deploy-hetzner-server/docs/topics.md +48 -0
  59. package/blueprints/deploy-hetzner-server/guide/deploy-hetzner-server.md +180 -0
  60. package/blueprints/edge-cloudflare-access/docs/topics.md +4 -1
  61. package/blueprints/edge-cloudflare-rate-limiting/docs/topics.md +4 -1
  62. package/blueprints/edge-cloudflare-tunnel/CHANGELOG.md +23 -0
  63. package/blueprints/edge-cloudflare-tunnel/README.md +257 -0
  64. package/blueprints/edge-cloudflare-tunnel/blueprint.json +168 -0
  65. package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +23 -0
  66. package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +19 -0
  67. package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +19 -0
  68. package/blueprints/edge-cloudflare-tunnel/contributions/probes/aud-presence-check.mjs +114 -0
  69. package/blueprints/edge-cloudflare-tunnel/contributions/probes/cloudflared-config-lint.mjs +80 -0
  70. package/blueprints/edge-cloudflare-tunnel/contributions/probes/manifest-schema-validate.mjs +237 -0
  71. package/blueprints/edge-cloudflare-tunnel/contributions/probes/probe-utils.mjs +231 -0
  72. package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-connector-healthy.mjs +143 -0
  73. package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-tunnel-hostname-routes.mjs +166 -0
  74. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-aud-presence-check.mjs +4 -0
  75. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-cloudflared-config-lint.mjs +4 -0
  76. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-manifest-schema-validate.mjs +4 -0
  77. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-connector-healthy.mjs +4 -0
  78. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-tunnel-hostname-routes.mjs +4 -0
  79. package/blueprints/edge-cloudflare-tunnel/contributions/probes/tunnel-manifest.schema.json +55 -0
  80. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-001.json +18 -0
  81. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-002.json +18 -0
  82. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-003.json +18 -0
  83. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-004.json +18 -0
  84. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-005.json +18 -0
  85. package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +26 -0
  86. package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +26 -0
  87. package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +25 -0
  88. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39101.json +24 -0
  89. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39102.json +24 -0
  90. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39103.json +24 -0
  91. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39104.json +24 -0
  92. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39105.json +24 -0
  93. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39106.json +24 -0
  94. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39107.json +24 -0
  95. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39108.json +24 -0
  96. package/blueprints/edge-cloudflare-tunnel/docs/topics.md +72 -0
  97. package/blueprints/edge-cloudflare-tunnel/guide/edge-cloudflare-tunnel.md +204 -0
  98. package/blueprints/edge-cloudflare-turnstile/docs/topics.md +4 -1
  99. package/blueprints/email-smtp-resend/docs/topics.md +4 -1
  100. package/blueprints/jobs-background/docs/topics.md +4 -1
  101. package/blueprints/messaging-queue-cloudflare/CHANGELOG.md +19 -0
  102. package/blueprints/messaging-queue-cloudflare/README.md +2 -2
  103. package/blueprints/messaging-queue-cloudflare/blueprint.json +1 -1
  104. package/blueprints/messaging-queue-cloudflare/contributions/probes/real-account-concurrency-smoke.mjs +192 -37
  105. package/blueprints/messaging-queue-cloudflare/docs/topics.md +4 -1
  106. package/blueprints/object-storage-s3/CHANGELOG.md +11 -0
  107. package/blueprints/object-storage-s3/README.md +14 -1
  108. package/blueprints/object-storage-s3/blueprint.json +12 -1
  109. package/blueprints/object-storage-s3/contributions/adrs/adr-2905-object-storage-s3-hetzner-object-storage-provider.json +30 -0
  110. package/blueprints/object-storage-s3/contributions/probes/hetzner-object-storage-round-trip.mjs +136 -0
  111. package/blueprints/object-storage-s3/contributions/probes/run-hetzner-object-storage-round-trip.mjs +11 -0
  112. package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-101.json +18 -0
  113. package/blueprints/object-storage-s3/contributions/tacs/tac-2904-object-storage-s3-hetzner-endpoint-helper.json +34 -0
  114. package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28110.json +25 -0
  115. package/blueprints/object-storage-s3/docs/topics.md +4 -1
  116. package/blueprints/object-storage-s3/guide/object-storage-s3.md +36 -0
  117. package/blueprints/observability-essentials/docs/topics.md +4 -1
  118. package/blueprints/observability-logging/docs/topics.md +4 -1
  119. package/blueprints/observability-probe-endpoints/docs/topics.md +4 -1
  120. package/blueprints/persistence-data-d1/docs/topics.md +4 -1
  121. package/blueprints/persistence-data-postgres/docs/topics.md +4 -1
  122. package/blueprints/persistence-data-sqlite/docs/topics.md +4 -1
  123. package/blueprints/platform-cloudflare-cron-triggers/docs/topics.md +4 -1
  124. package/blueprints/platform-cloudflare-durable-objects/CHANGELOG.md +15 -0
  125. package/blueprints/platform-cloudflare-durable-objects/blueprint.json +1 -1
  126. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/namespace-facade-ready.mjs +3 -3
  127. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/probe-utils.mjs +28 -14
  128. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/real-account-storage-smoke.mjs +132 -26
  129. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/sole-reader-scan.mjs +40 -39
  130. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/storage-round-trip.mjs +26 -16
  131. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/websocket-hub-broadcast.mjs +31 -34
  132. package/blueprints/platform-cloudflare-durable-objects/docs/topics.md +4 -1
  133. package/blueprints/platform-cloudflare-kv/CHANGELOG.md +40 -1
  134. package/blueprints/platform-cloudflare-kv/README.md +41 -13
  135. package/blueprints/platform-cloudflare-kv/blueprint.json +1 -1
  136. package/blueprints/platform-cloudflare-kv/contributions/probes/cache-aside-hit-then-miss.mjs +26 -26
  137. package/blueprints/platform-cloudflare-kv/contributions/probes/event-secrecy.mjs +49 -31
  138. package/blueprints/platform-cloudflare-kv/contributions/probes/facade-round-trip.mjs +58 -16
  139. package/blueprints/platform-cloudflare-kv/contributions/probes/list-with-prefix.mjs +6 -5
  140. package/blueprints/platform-cloudflare-kv/contributions/probes/real-account-eventual-consistency-smoke.mjs +131 -67
  141. package/blueprints/platform-cloudflare-kv/docs/topics.md +4 -1
  142. package/blueprints/platform-docker-compose-host/CHANGELOG.md +12 -0
  143. package/blueprints/platform-docker-compose-host/README.md +76 -0
  144. package/blueprints/platform-docker-compose-host/blueprint.json +209 -0
  145. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3901-platform-docker-compose-host-contract.json +14 -0
  146. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +14 -0
  147. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +14 -0
  148. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3904-platform-docker-compose-host-log-driver.json +14 -0
  149. package/blueprints/platform-docker-compose-host/contributions/probes/caddyfile-validate.mjs +108 -0
  150. package/blueprints/platform-docker-compose-host/contributions/probes/compose-config-lint.mjs +231 -0
  151. package/blueprints/platform-docker-compose-host/contributions/probes/probe-utils.mjs +185 -0
  152. package/blueprints/platform-docker-compose-host/contributions/probes/real-account-minimal-stack-up.mjs +41 -0
  153. package/blueprints/platform-docker-compose-host/contributions/probes/real-account-reload-burst.mjs +36 -0
  154. package/blueprints/platform-docker-compose-host/contributions/probes/run-caddyfile-validate.mjs +5 -0
  155. package/blueprints/platform-docker-compose-host/contributions/probes/run-compose-config-lint.mjs +5 -0
  156. package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-minimal-stack-up.mjs +5 -0
  157. package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-reload-burst.mjs +5 -0
  158. package/blueprints/platform-docker-compose-host/contributions/probes/run-secrets-as-files-scan.mjs +5 -0
  159. package/blueprints/platform-docker-compose-host/contributions/probes/secrets-as-files-scan.mjs +116 -0
  160. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-001.json +18 -0
  161. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-002.json +18 -0
  162. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-003.json +18 -0
  163. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-004.json +18 -0
  164. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-005.json +18 -0
  165. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-006.json +18 -0
  166. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +26 -0
  167. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +26 -0
  168. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +26 -0
  169. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +26 -0
  170. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38101.json +24 -0
  171. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38102.json +24 -0
  172. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38103.json +24 -0
  173. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38104.json +24 -0
  174. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38105.json +25 -0
  175. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38106.json +24 -0
  176. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38107.json +24 -0
  177. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38108.json +24 -0
  178. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38109.json +24 -0
  179. package/blueprints/platform-docker-compose-host/docs/topics.md +71 -0
  180. package/blueprints/platform-docker-compose-host/guide/platform-docker-compose-host.md +94 -0
  181. package/blueprints/security-auth-clerk/docs/topics.md +4 -1
  182. package/blueprints/security-auth-keycloak/docs/topics.md +4 -1
  183. package/blueprints/security-auth-magic-link/docs/topics.md +4 -1
  184. package/blueprints/security-auth-oauth2/docs/topics.md +4 -1
  185. package/blueprints/security-secrets-management/docs/topics.md +4 -1
  186. package/fixtures/canary-manifest.json +9 -9
  187. package/guidance/harness-template.md +67 -0
  188. package/guidance/managed/agent-instructions-block.hash +1 -1
  189. package/guidance/managed/agent-instructions-block.md +67 -0
  190. package/package.json +2 -2
  191. package/rcf/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +13 -0
  192. package/rcf/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +13 -0
  193. package/rcf/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +13 -0
  194. package/rcf/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +13 -0
  195. package/rcf/adrs/adr-3901-platform-docker-compose-host-contract.json +13 -0
  196. package/rcf/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +13 -0
  197. package/rcf/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +13 -0
  198. package/rcf/adrs/adr-3904-platform-docker-compose-host-log-driver.json +13 -0
  199. package/rcf/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +13 -0
  200. package/rcf/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +13 -0
  201. package/rcf/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +13 -0
  202. package/rcf/code-nodes/cn-410.json +20 -0
  203. package/rcf/code-nodes/cn-440.json +20 -0
  204. package/rcf/code-nodes/cn-470.json +19 -0
  205. package/rcf/code-nodes/cn-500.json +12 -0
  206. package/rcf/code-nodes/cn-510.json +12 -0
  207. package/rcf/code-nodes/cn-520.json +14 -0
  208. package/rcf/code-nodes/cn-521.json +12 -0
  209. package/rcf/code-nodes/cn-522.json +13 -0
  210. package/rcf/code-nodes/cn-523.json +12 -0
  211. package/rcf/code-nodes/cn-524.json +14 -0
  212. package/rcf/code-nodes/cn-525.json +12 -0
  213. package/rcf/code-nodes/cn-526.json +14 -0
  214. package/rcf/fbs/fbs-130.json +23 -0
  215. package/rcf/fbs/fbs-140.json +23 -0
  216. package/rcf/fbs/fbs-150.json +22 -0
  217. package/rcf/fbs/fbs-160.json +15 -0
  218. package/rcf/fbs/fbs-165.json +15 -0
  219. package/rcf/fbs/fbs-170.json +16 -0
  220. package/rcf/fbs/fbs-171.json +16 -0
  221. package/rcf/fbs/fbs-172.json +15 -0
  222. package/rcf/fbs/fbs-173.json +17 -0
  223. package/rcf/fbs/fbs-174.json +17 -0
  224. package/rcf/requirements/req-110.json +58 -0
  225. package/rcf/requirements/req-111.json +32 -0
  226. package/rcf/requirements/req-112.json +42 -0
  227. package/rcf/requirements/req-113.json +33 -0
  228. package/rcf/requirements/req-114.json +20 -0
  229. package/rcf/requirements/req-115.json +38 -0
  230. package/rcf/requirements/req-120.json +20 -0
  231. package/rcf/requirements/req-121.json +20 -0
  232. package/rcf/requirements/req-122.json +20 -0
  233. package/rcf/requirements/req-123.json +42 -0
  234. package/rcf/requirements/req-124.json +27 -0
  235. package/rcf/requirements/req-125.json +20 -0
  236. package/rcf/requirements/req-130.json +32 -0
  237. package/rcf/requirements/req-131.json +54 -0
  238. package/rcf/requirements/req-132.json +27 -0
  239. package/rcf/requirements/req-133.json +20 -0
  240. package/rcf/requirements/req-134.json +42 -0
  241. package/rcf/requirements/req-140.json +149 -0
  242. package/rcf/requirements/req-145.json +53 -0
  243. package/rcf/requirements/req-150.json +20 -0
  244. package/rcf/requirements/req-151.json +85 -0
  245. package/rcf/requirements/req-152.json +20 -0
  246. package/rcf/requirements/req-153.json +59 -0
  247. package/rcf/requirements/req-154.json +44 -0
  248. package/rcf/tacs/tac-3801-deploy-hetzner-server-provisioner.json +17 -0
  249. package/rcf/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +17 -0
  250. package/rcf/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +17 -0
  251. package/rcf/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +16 -0
  252. package/rcf/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +17 -0
  253. package/rcf/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +17 -0
  254. package/rcf/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +17 -0
  255. package/rcf/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +17 -0
  256. package/rcf/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +17 -0
  257. package/rcf/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +17 -0
  258. package/rcf/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +16 -0
  259. package/rcf/test-suites/ts-071.json +2 -2
  260. package/rcf/test-suites/ts-086.json +2 -2
  261. package/rcf/test-suites/ts-140.json +86 -0
  262. package/rcf/test-suites/ts-150.json +86 -0
  263. package/rcf/test-suites/ts-160.json +78 -0
  264. package/rcf/test-suites/ts-170.json +22 -0
  265. package/rcf/test-suites/ts-175.json +22 -0
  266. package/rcf/test-suites/ts-180.json +30 -0
  267. package/rcf/test-suites/ts-181.json +30 -0
  268. package/rcf/test-suites/ts-182.json +22 -0
  269. package/rcf/test-suites/ts-183.json +38 -0
  270. package/rcf/test-suites/ts-184.json +38 -0
  271. package/rcf/user-stories/us-11001.json +23 -0
  272. package/rcf/user-stories/us-11101.json +23 -0
  273. package/rcf/user-stories/us-11102.json +24 -0
  274. package/rcf/user-stories/us-11201.json +23 -0
  275. package/rcf/user-stories/us-11202.json +23 -0
  276. package/rcf/user-stories/us-11301.json +24 -0
  277. package/rcf/user-stories/us-11401.json +23 -0
  278. package/rcf/user-stories/us-11402.json +23 -0
  279. package/rcf/user-stories/us-11501.json +23 -0
  280. package/rcf/user-stories/us-12001.json +23 -0
  281. package/rcf/user-stories/us-12101.json +23 -0
  282. package/rcf/user-stories/us-12102.json +23 -0
  283. package/rcf/user-stories/us-12201.json +23 -0
  284. package/rcf/user-stories/us-12202.json +24 -0
  285. package/rcf/user-stories/us-12301.json +23 -0
  286. package/rcf/user-stories/us-12401.json +23 -0
  287. package/rcf/user-stories/us-12402.json +23 -0
  288. package/rcf/user-stories/us-12501.json +23 -0
  289. package/rcf/user-stories/us-13001.json +23 -0
  290. package/rcf/user-stories/us-13002.json +23 -0
  291. package/rcf/user-stories/us-13101.json +23 -0
  292. package/rcf/user-stories/us-13102.json +23 -0
  293. package/rcf/user-stories/us-13201.json +23 -0
  294. package/rcf/user-stories/us-13301.json +23 -0
  295. package/rcf/user-stories/us-13302.json +23 -0
  296. package/rcf/user-stories/us-13401.json +23 -0
  297. package/rcf/user-stories/us-14001.json +21 -0
  298. package/rcf/user-stories/us-14501.json +24 -0
  299. package/rcf/user-stories/us-15001.json +26 -0
  300. package/rcf/user-stories/us-15101.json +26 -0
  301. package/rcf/user-stories/us-15201.json +21 -0
  302. package/rcf/user-stories/us-15301.json +31 -0
  303. package/rcf/user-stories/us-15401.json +31 -0
  304. package/releases/releases.yaml +21 -1
  305. package/src/blueprint/apply.js +64 -0
  306. package/src/blueprint/consistency-lint.js +432 -0
  307. package/src/blueprint/disposition-ledger.js +224 -0
  308. package/src/blueprint/dispositions.js +44 -0
  309. package/src/blueprint/index.js +19 -0
  310. package/src/browser-verify/manifest-writer.js +53 -2
  311. package/src/browser-verify/pack-runner.js +18 -0
  312. package/src/cli/blueprint.js +103 -0
  313. package/src/setup/identity-seed.js +18 -0
@@ -0,0 +1,180 @@
1
+ # deploy-hetzner-server guide
2
+
3
+ Practical guide for the operator applying `deploy-hetzner-server`
4
+ v1.0.0. Covers manifest authoring, the two provisioner paths, the
5
+ hardening baseline, snapshot posture, elicitation shortcuts, and
6
+ the runtime probe rig.
7
+
8
+ ## Prerequisites
9
+
10
+ - A Hetzner Cloud project and API token. The token binds to one
11
+ project; a fresh throwaway project keeps the blast radius small
12
+ during onboarding.
13
+ - `hcloud` on `PATH` when `provisioning-tool` is `hcloud`. Install:
14
+ Homebrew `brew install hcloud` on macOS, or download the OS/arch
15
+ tarball from https://github.com/hetznercloud/cli/releases/latest,
16
+ verify against `checksums.txt`, drop the binary on `PATH`. Skip
17
+ the install when `provisioning-tool` is `raw-api`; the provisioner
18
+ reaches the REST API directly.
19
+ - `security-secrets-management` applied so the facade reads
20
+ `HETZNER_ACCOUNT_API_KEY` from the operator's vault of choice.
21
+
22
+ ## Authoring `hetzner/servers/<name>.json`
23
+
24
+ Every server declaration lives at
25
+ `hetzner/servers/<server-name>.json` and validates against the
26
+ shipped `hetzner-server.schema.json`. The fixture manifest at
27
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/hetzner/servers/ci-throwaway.json`
28
+ is the canonical example. Copy it and edit:
29
+
30
+ ```jsonc
31
+ {
32
+ "name": "ops-01",
33
+ "serverType": "cx33",
34
+ "location": "fsn1",
35
+ "image": "ubuntu-24.04",
36
+ "sshKeyIds": ["dave-ed25519", "baz-yubikey"],
37
+ "networkId": null,
38
+ "firewallId": null,
39
+ "cloudInitPath": "hetzner/servers/rendered/ops-01.cloud-init.yaml",
40
+ "labels": {
41
+ "role": "ops-host",
42
+ "blueprint": "deploy-hetzner-server"
43
+ },
44
+ "firewallRules": [
45
+ { "name": "ssh", "protocol": "tcp", "port": 22, "direction": "in", "sourceIps": ["203.0.113.0/24"] },
46
+ { "name": "http", "protocol": "tcp", "port": 80, "direction": "in", "sourceIps": ["0.0.0.0/0", "::/0"] },
47
+ { "name": "https", "protocol": "tcp", "port": 443, "direction": "in", "sourceIps": ["0.0.0.0/0", "::/0"] }
48
+ ],
49
+ "snapshotCadence": "weekly",
50
+ "hetznerManagedBackups": false
51
+ }
52
+ ```
53
+
54
+ The manifest-schema-validate probe refuses on missing required
55
+ fields and on a rule set that opens 22 to `0.0.0.0/0`.
56
+
57
+ ## Rendering cloud-init
58
+
59
+ Point `cloudInitPath` at a rendered file the provisioner uploads
60
+ with `--user-data-from-file`. The fixture renderer at
61
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/src/cloud-init-renderer.mjs`
62
+ consumes the shipped template with two placeholders:
63
+
64
+ - `{{name}}` (the manifest name)
65
+ - `{{sshAuthorizedKeysBlock}}` (the ssh-authorized_keys entries per
66
+ `sshKeyIds`)
67
+
68
+ An applying project runs the renderer at manifest edit time (a git
69
+ pre-commit hook, or a `pnpm build:cloud-init` script) and commits
70
+ the rendered YAML alongside the manifest.
71
+
72
+ ## Six hardening baseline blocks (ADR-3804)
73
+
74
+ The rendered YAML lands the six blocks per the estate baseline at
75
+ `operator/knowledge/systems/environments/hetzner-cloud/overview.md`
76
+ Security posture baseline and the Debian unattended-upgrades wiki
77
+ at https://wiki.debian.org/UnattendedUpgrades.
78
+
79
+ 1. SSH key-only. `PasswordAuthentication no`.
80
+ 2. Root disabled. `PermitRootLogin no`.
81
+ 3. UFW default-deny incoming. `ufw allow 22/tcp 80/tcp 443/tcp`.
82
+ 4. DOCKER-USER iptables chain. Drops non-conforming egress from
83
+ containers before Docker rules apply.
84
+ 5. fail2ban SSH jail on port 22. `maxretry 5`, `bantime 3600`.
85
+ 6. `unattended-upgrades` on for security-only updates
86
+ (`Unattended-Upgrade::Automatic-Reboot "false"`).
87
+
88
+ Deviation costs an ADR at the applying project level; the shipped
89
+ lint refuses on drift.
90
+
91
+ ## When to reach for hcloud vs raw-api
92
+
93
+ - `hcloud` (default). Choose it when the operator laptop and CI
94
+ runner can install the binary. Small, self-documenting, JSON
95
+ output on `--output json`; the vendor updates it in step with the
96
+ API. Docs: https://github.com/hetznercloud/cli.
97
+ - `raw-api`. Choose it when a container base image cannot install
98
+ `hcloud` (Alpine minimal builds, a locked-down runtime). The
99
+ provisioner reaches the REST endpoints directly per
100
+ https://docs.hetzner.cloud/. Vendor SLAs cover the API; the local
101
+ CLI is a convenience layer.
102
+
103
+ The facade shape is identical on both paths so switching is a one
104
+ elicit change.
105
+
106
+ ## When to reach for `cx23` vs `cx33`
107
+
108
+ - `cx23` (default). Two vCPU, 4 GB RAM, 40 GB SSD. Approximately
109
+ EUR 4.15/month. Fine for the docker-compose stack that ops-01
110
+ runs (fewer than ten containers, each below 300 MB RSS).
111
+ - `cx33`. Two vCPU, 8 GB RAM, 80 GB SSD. Approximately EUR
112
+ 7.99/month. Reach when the compose stack holds a Postgres, a
113
+ Grafana, or any container that regularly touches 1 GB RSS.
114
+ - `cx43`. Four vCPU, 16 GB RAM. Reach for a home for the
115
+ `platform-cloudflare-durable-objects` self-hosted mode or a
116
+ Postgres + Grafana + Loki triad.
117
+ - `cpx*`. AMD EPYC. Faster per vCPU; slightly more expensive.
118
+ - `cax*`. ARM64. Choose when every runtime component has an ARM
119
+ build (Node, Postgres, cloudflared, most caddy modules do).
120
+
121
+ Vendor pricing:
122
+ https://docs.hetzner.com/cloud/servers/overview.
123
+
124
+ ## Snapshot cadence choices (ADR-3802)
125
+
126
+ - `weekly` (default). One snapshot every Sunday at 02:00 local. On
127
+ a `cx23`, adds approximately EUR 0.005/month in snapshot storage.
128
+ - `daily`. Higher recovery-point objective. On a `cx23`, adds
129
+ approximately EUR 0.035/month.
130
+ - `off`. No automatic snapshot. Choose only when Hetzner-managed
131
+ backups are opted-in AND the application layer takes its own
132
+ logical backups on cadence.
133
+
134
+ Hetzner-managed backups are a separately elicited paid opt-in at
135
+ approximately 20 percent of monthly server price (on a `cx23`,
136
+ approximately EUR 0.80/month). They compose with the scheduled
137
+ snapshot runner; both fire independently.
138
+ Vendor pricing:
139
+ https://docs.hetzner.com/cloud/servers/backups-snapshots/overview.
140
+
141
+ ## Snapshot-on-demand
142
+
143
+ Fire a snapshot manually from the applying project's CLI:
144
+
145
+ ```
146
+ node ./scripts/snapshot.mjs --server ops-01
147
+ ```
148
+
149
+ The script imports the provisioner facade, calls `takeSnapshot`,
150
+ and emits `hetznerSnapshotTaken` on the injected sink. The account-
151
+ bound probe `real-account-snapshot-on-demand` covers the same shape.
152
+
153
+ ## The shared throwaway-server fixture
154
+
155
+ Every real-account probe runs against a throwaway `cx23` in `fsn1`
156
+ provisioned from the fixture manifest. Fixture location:
157
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/`. Cost
158
+ ceiling: EUR 0.006/hour per run; destroy runs in `always()`; nightly
159
+ `sweep-orphans` cron collects any leak on a 60-minute label-age
160
+ cutoff.
161
+
162
+ Round-7 T-2 (`platform-docker-compose-host`) and T-3
163
+ (`edge-cloudflare-tunnel`) EXTEND this fixture rather than shipping
164
+ a second copy. T-2 mounts the docker + compose verbs on the same
165
+ throwaway server after cloud-init seals the baseline; T-3 mounts
166
+ `cloudflared` on the runtime T-2 stands up.
167
+
168
+ ## Standards trace
169
+
170
+ Every URL fetched 200 in the ratifying pass before the T-1 PR.
171
+
172
+ - Hetzner Cloud API https://docs.hetzner.cloud/
173
+ - Hetzner Cloud CLI https://github.com/hetznercloud/cli
174
+ - Hetzner Cloud servers overview https://docs.hetzner.com/cloud/servers/overview
175
+ - Hetzner Cloud firewalls overview https://docs.hetzner.com/cloud/firewalls/overview/
176
+ - Hetzner Cloud backups-and-snapshots overview https://docs.hetzner.com/cloud/servers/backups-snapshots/overview
177
+ - Hetzner Cloud networks overview https://docs.hetzner.com/cloud/networks/overview/
178
+ - Hetzner community basic-cloud-config tutorial https://community.hetzner.com/tutorials/basic-cloud-config
179
+ - cloud-init https://cloudinit.readthedocs.io/en/latest/
180
+ - Debian unattended-upgrades wiki https://wiki.debian.org/UnattendedUpgrades
@@ -46,7 +46,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
46
46
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
47
47
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
48
48
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
49
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
49
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
50
50
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
51
51
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
52
52
  | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
@@ -55,6 +55,9 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
55
55
  | edge-cloudflare-access | 34101-34899 | 35xx | shipped v1.0.0 | `edgeAuthenticationGate` |
56
56
  | edge-cloudflare-turnstile | 35101-35899 | 36xx | shipped v1.0.0 | `humanVerificationGate` |
57
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` |
58
61
 
59
62
  US 34101-34109 sit at the LOW end of the 34101-34899 band on purpose (watchpost run-4 lesson). ADR/TAC suffixes for this blueprint use the 3501-3599 block, continuing the shelf pattern.
60
63
 
@@ -5,6 +5,9 @@ This file is the edge-cloudflare-rate-limiting half of the cross-blueprint contr
5
5
  ## Global ADR topics this blueprint contributes (exact strings)
6
6
 
7
7
  | Topic string | edge-cloudflare-rate-limiting contribution | Origin | Composition note |
8
+ | deploy-hetzner-server | 37101-37899 | 38xx | shipped v1.0.0 | `linuxCloudHostContract`, `snapshotAndBackupCadence` |
9
+ | platform-docker-compose-host | 38101-38899 | 39xx | shipped v1.0.0 | `containerHostContract` |
10
+ | edge-cloudflare-tunnel | 39101-39899 | 40xx | shipped v1.0.0 | `edgeIngressBridge` |
8
11
  |---|---|---|---|
9
12
  | `edgeThrottleContract` | ADR-3701-edge-cloudflare-rate-limiting-throttle-contract | Minted by this blueprint. Names the wire-shape contract for the project-wide edge throttle; the shipped answer is Cloudflare WAF rate-limiting rules per `https://developers.cloudflare.com/waf/rate-limiting-rules/`. | The one project-wide decision on the edge throttle at the shipped-shape boundary. A future non-Cloudflare sibling (Fastly rate-limits, Vercel Edge Config throttles) would contribute the same topic string with a different answer, forcing a DELIBERATE conflict the operator resolves with a project-level ADR. |
10
13
 
@@ -46,7 +49,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
46
49
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
47
50
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
48
51
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
49
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
52
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
50
53
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
51
54
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
52
55
  | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
@@ -0,0 +1,23 @@
1
+ # edge-cloudflare-tunnel CHANGELOG
2
+
3
+ ## 1.0.1 - 2026-09-08
4
+
5
+ H-2 hardening train (`h2-cf-platform-probe-integrity`): probe-integrity patch. No capability change.
6
+
7
+ - Replaced the warn-on-env-set stub in `real-account-connector-healthy.mjs` with a real cloudflared connector-health driver via the new cf-edge shim (`packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs`). With both `CI_HAS_CLOUDFLARE_ACCOUNT` and `CI_HAS_HETZNER_ACCOUNT` set, the driver provisions the throwaway server through the hetzner fixture surface (H-1 territory, consumed as-is), runs `cloudflared tunnel info <name>` via the shim, asserts healthy connector count, captures the `tunnelConnectorUp` event, and always tears the server down. Env-absent branch keeps pass-with-skip. Env-present-with-fixture-failure yields FAIL with a pointer to the fixture step, never warn.
8
+ - Replaced both warn-on-env-set stubs in `real-account-tunnel-hostname-routes.mjs` with real drivers. Public-hostname sub-case: undici fetch against the scratch subdomain via the shim's `fetchTunnelHostname` seam, activated on `CI_HAS_CLOUDFLARE_ACCOUNT`. Access-gated sub-case: two-identity HS256 JWT check, activated only when `CI_HAS_CLOUDFLARE_ACCESS` is ALSO set (amendment 4). Env-absent branch keeps pass-with-skip. A pass is no longer reachable from `CI_HAS_CLOUDFLARE_ACCOUNT` presence alone.
9
+ - Converted the credentials-placeholder-read-failed branch in `manifest-schema-validate.mjs::eventSecrecyScan` from warn to fail with a pointer to the exact fixture path (`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/*/credentials/probe.json.example`) and the setup requirement (must exist and parse as JSON).
10
+ - Added the cf-edge tunnel shim (`packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs`) with five seams: `provisionScratchServer`, `destroyScratchServer`, `cloudflaredTunnelInfo`, `fetchTunnelHostname`, `mintScratchIdentity`. The shim reads `H2_CF_TUNNEL_SHIM_MODE` and `H2_CF_TUNNEL_HOSTNAME_SHIM_MODE` for synthetic-mode branches; the probe bodies hold zero `SIMULATE_` token references.
11
+ - Tunnel symbolic-anchor ruling (task C4): the `AC-tunnel-*` symbolic ids on the five tunnel probes ARE the shipped AC ids in the `US-39101..US-39108` contribution band, so no re-anchor was performed. Full mapping in the H-2 PR body.
12
+
13
+ ## 1.0.0 - 2026-09-08
14
+
15
+ Initial release. Round-7 T-3 of the Hetzner spec at `projects/blueprint-library/specs/hetzner-round-7-spec-2026-09-07.md`.
16
+
17
+ - Mints capability `tunnelBridge` and global topic `edgeIngressBridge`.
18
+ - Five REQs, eight USs, three TACs, three ADRs; five Node-only probes.
19
+ - Extends the shared throwaway-Hetzner-server fixture with a cloudflared connector in both runtime shapes (compose-service alongside the T-2 web and caddy services when `containerHost` is applied; systemd-unit for a bare `cloudHost`) and both hostname modes (access-gated when `zeroTrustGate` is applied; public-hostname when absent) plus five `run-<probe>.mjs` delegate shims.
20
+ - manifest-schema-validate carries five fixture-side mutation switches (`SIMULATE_MANIFEST_INVALID_TUNNEL_ID`, `SIMULATE_MANIFEST_CREDENTIALS_INLINE`, `SIMULATE_MANIFEST_MISSING_CATCHALL`, `SIMULATE_ORIGIN_PORT_OPEN`, `SIMULATE_EVENT_SECRECY_LEAK`); cloudflared-config-lint carries `SIMULATE_INGRESS_INVALID` and runs `cloudflared tunnel ingress validate` via the `cloudflare/cloudflared:2026.8.3` container when a local binary is not on PATH; aud-presence-check carries `SIMULATE_AUD_DROP`.
21
+ - Mutation-discipline (T-2 gate ruling carried forward): every `SIMULATE_*` switch lives in the fixture-side delegate shim; the probe modules read only `RCF_LITE_T3_FIXTURE_ROOT` and never branch on being under mutation.
22
+ - Connector-runtime choice elicited via `connector-runtime` (compose-service default when `containerHost` applied; systemd-unit alternative for bare `cloudHost`, per the vendor cloudflared as-a-service local-configuration-file docs); hostname-mode discovered (not elicited) from `appliedCapabilities` via the sidecar per ADR-4003.
23
+ - Consumers: `w-2026-09-06-dave-010` follow-on (make the librarian API and workspace viewer reachable without Mullvad) is the estate consumer; blueprint acceptance never depends on that consumer, and the ops-01 migration mints as its own work item.
@@ -0,0 +1,257 @@
1
+ # edge-cloudflare-tunnel
2
+
3
+ Round-7 T-3 core blueprint on the rcf-lite shelf. Bridges a service on a
4
+ `cloudHost` (bare, systemd shape) or a `containerHost` (compose-service
5
+ shape) to the Cloudflare edge through a Cloudflare Tunnel, with no
6
+ public origin ports. When applied alongside `edge-cloudflare-access`
7
+ (via the `zeroTrustGate` capability) the tunnel binds an Access AUD;
8
+ when applied without it the tunnel serves in public-hostname mode.
9
+
10
+ - Slug: `edge-cloudflare-tunnel`
11
+ - Version: `1.0.0`
12
+ - Category: `edge`
13
+ - Capabilities: `["tunnelBridge"]`
14
+ - Mints global topic: `edgeIngressBridge`
15
+ - Contribution count: 5 REQs, 8 USs, 3 TACs, 3 ADRs, 5 probes
16
+ - Consumers: ops-01 has no consumer today (SSH is the only inbound path). Estate consumer is the follow-on make-the-librarian-API-and-workspace-viewer-reachable-without-Mullvad work that `w-2026-09-06-dave-010` is expected to green-light. Blueprint acceptance never depends on that consumer; the ops-01 migration mints as a separate work item at that point (`w-2026-09-07-dave-004`).
17
+
18
+ ## Five REQs
19
+
20
+ | Id | Contract |
21
+ |---|---|
22
+ | edge-cloudflare-tunnel-REQ-001 | Connector install shape switches on applied `containerHost` (compose-service default; systemd-unit for bare `cloudHost`); `cloudflaredReady` fires on boot with connector id, region and version metadata. |
23
+ | edge-cloudflare-tunnel-REQ-002 | Tunnel manifest schema at `cloudflare/tunnels/<name>.yaml` (uuid tunnel id, `credentialsFile.secretRef` reference, ingress rules with catch-all `http_status:404`). |
24
+ | edge-cloudflare-tunnel-REQ-003 | No public origin ports on the host; T-1 firewall stays closed on 80/443 unless a reverse proxy is applied; manifest refuses ingress rules whose service URL binds to a host public interface. |
25
+ | edge-cloudflare-tunnel-REQ-004 | Access-gated composition: `zeroTrustGate` applied attaches `originRequest.access.aud`; absent omits the block and serves in public-hostname mode. |
26
+ | edge-cloudflare-tunnel-REQ-005 | Credentials-file discipline (host mode 0o400, referenced via `secretRef`, not committed, grep-refuse, no leak in event bodies). |
27
+
28
+ ## Connector runtime shapes
29
+
30
+ The connector runtime is elicited via `connector-runtime` (ADR-4002) with
31
+ `recommendedDefault: compose-service`.
32
+
33
+ - **compose-service** (default when `containerHost` is applied). The
34
+ cloudflared container ships alongside the applying project services on
35
+ the same web-net network. No host ports; restart `unless-stopped`;
36
+ logging driver aligned with the T-2 compose-host log-driver elicit.
37
+ Fixture: `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/compose-service/`.
38
+ - **systemd-unit** (alternative for a bare `cloudHost`). The cloudflared
39
+ binary is installed via the vendor package per the cloudflared
40
+ as-a-service documented systemd unit and runs as a systemd service
41
+ reading the local configuration file. Fixture:
42
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/systemd-unit/`.
43
+
44
+ When to reach for which: pick compose-service when the project already
45
+ runs docker compose on the host (natural home, same log driver, same
46
+ restart discipline); pick systemd-unit when the host runs bare and there
47
+ is no compose stack to add a service to (or when the project deliberately
48
+ keeps cloudflared outside the compose lifecycle for boot ordering).
49
+
50
+ ## Tunnel manifest shape
51
+
52
+ The manifest lives at `cloudflare/tunnels/<name>.yaml` under the
53
+ applying project. The shipped JSON schema
54
+ (`contributions/probes/tunnel-manifest.schema.json`) requires:
55
+
56
+ - `tunnel`: uuid-shaped Cloudflare Tunnel id.
57
+ - `credentialsFile.secretRef`: reference into `security-secrets-management`
58
+ (never a literal path or JSON body inline).
59
+ - `ingress`: array of `{ hostname?, service, originRequest? }`. The last
60
+ entry MUST be a catch-all `service: http_status:404`. When the
61
+ Access-gated shape is applied every non-catch-all rule attaches
62
+ `originRequest.access.aud` with the AUD from the sidecar.
63
+
64
+ Example (compose-service, public-hostname mode):
65
+
66
+ ```yaml
67
+ tunnel: 00000000-0000-4000-8000-0000000012ab
68
+ credentialsFile:
69
+ secretRef: hq-estate/CLOUDFLARE_TUNNEL_CREDENTIALS_MY_PROJECT
70
+ ingress:
71
+ - hostname: my-app.example.com
72
+ service: http://web:8080
73
+ - service: http_status:404
74
+ ```
75
+
76
+ Example (Access-gated shape adds one block per non-catch-all rule):
77
+
78
+ ```yaml
79
+ ingress:
80
+ - hostname: my-app.example.com
81
+ service: http://web:8080
82
+ originRequest:
83
+ access:
84
+ aud: AUD-example-cloudflare-team
85
+ teamName: my-team
86
+ required: true
87
+ - service: http_status:404
88
+ ```
89
+
90
+ ## Credentials-file discipline
91
+
92
+ The tunnel credentials JSON file lands on the host at mode 0o400 (owner
93
+ read only). The manifest references it as a `credentialsFile.secretRef`;
94
+ the fixture placeholder at
95
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/*/credentials/probe.json.example`
96
+ documents the pattern. The credentials file MUST NOT be committed to
97
+ the applying project tree; a grep across the working tree for the
98
+ tunnel id and account tag literals refuses on any tracked file. Every
99
+ `cloudflaredReady`, `tunnelConnectorUp` and `ingressRuleReloaded` event
100
+ body carries metadata only; the event-secrecy scan refuses on a body
101
+ that mentions the credentials literal.
102
+
103
+ Rotate credentials by rewriting the secret pointed to by `secretRef`
104
+ and re-applying; the connector reloads on the next boot cycle.
105
+
106
+ ## Composition point (Access-gated vs public-hostname)
107
+
108
+ The hostname mode is discovered from the applied capability set (not
109
+ elicited; see ADR-4003). The blueprint reads the sidecar at
110
+ `rcf/blueprints/edge-cloudflare-tunnel.applied.json` and inspects
111
+ `appliedCapabilities`.
112
+
113
+ - When `zeroTrustGate` is present (i.e. `edge-cloudflare-access` v1.0.0
114
+ is applied on the same project), every ingress rule attaches
115
+ `originRequest.access.aud` and cloudflared refuses at the edge any
116
+ request without a valid Access JWT for that AUD.
117
+ - When `zeroTrustGate` is absent, the block is omitted and the tunnel
118
+ serves in public-hostname mode (open ingress on the public hostname;
119
+ authorization is whatever the origin service itself carries).
120
+
121
+ The `aud-presence-check` probe refuses when the gated variant drifts to
122
+ omit the AUD block (defensive against silent degradation).
123
+
124
+ Graceful degrade: when Access is not applied (T-4 absent from
125
+ `origin/main` or the applying project simply does not want the gate),
126
+ the blueprint still ships in public-hostname mode. The probe assertion
127
+ runs on both fixture variants so the shape flip is proven on every
128
+ reviewer boot.
129
+
130
+ ## No-public-ports rule
131
+
132
+ The T-1 firewall on a `cloudHost` allows 22 unconditionally and 80/443
133
+ only when a reverse proxy is applied. A project applying only T-1 plus
134
+ T-3 (no reverse proxy) declares zero 80/443 rules; the tunnel is the
135
+ sole path from the edge to the origin. The manifest schema refuses
136
+ ingress rules whose service URL binds to `0.0.0.0`, a non-loopback
137
+ numeric IP, or an unresolvable shape.
138
+
139
+ ## Elicited parameters
140
+
141
+ - `tunnel-name` (string; default `probe`): the Cloudflare Tunnel name
142
+ and the manifest filename under `cloudflare/tunnels/<name>.yaml`.
143
+ - `cloudflare-zone-secret` (string; default `hq-estate/CLOUDFLARE_ZONE_ID`):
144
+ `security-secrets-management` reference to the Cloudflare zone the
145
+ tunnel serves under.
146
+ - `public-hostname-template` (string; default `<service>.<zone>`):
147
+ template for the public hostname, expanded per ingress rule.
148
+ - `ingress-rules` (string; default `[]`): ingress rules array (hostname
149
+ + service URL pairs).
150
+ - `access-aud` (string; default empty): Access AUD tag for the gated
151
+ shape. Presence toggles the gated shape when `zeroTrustGate` is
152
+ applied.
153
+ - `connector-runtime` (enum: `compose-service`, `systemd-unit`; default
154
+ `compose-service`): the connector runtime per ADR-4002.
155
+
156
+ ## Companions
157
+
158
+ - `suggestedCompanions[logging]`: every `cloudflaredReady`,
159
+ `tunnelConnectorUp`, `tunnelConnectorDown`, `ingressRuleReloaded` and
160
+ `accessGateRefused` event writes through the applied logger.
161
+ - `suggestedCompanions[errorHandling]`: a cloudflared exit, a JWT-check
162
+ failure, an ingress-rule schema violation, a hostname-DNS-route API
163
+ refusal constructs an internal error record.
164
+
165
+ `providesRoles: []`.
166
+
167
+ ## Five probes
168
+
169
+ Every probe writes its report envelope to
170
+ `.rcf/reports/blueprints/edge-cloudflare-tunnel/<probe-name>.json`
171
+ under the repo root.
172
+
173
+ - **manifest-schema-validate** (local). Anchors AC-tunnel-manifestSchema,
174
+ AC-tunnel-noPublicOrigin, AC-tunnel-credentialsDiscipline. `accountBound: false`.
175
+ Ajv-free walker over the shipped JSON schema; runs on all four
176
+ variants; refuses on `SIMULATE_MANIFEST_INVALID_TUNNEL_ID`,
177
+ `SIMULATE_MANIFEST_CREDENTIALS_INLINE`, `SIMULATE_MANIFEST_MISSING_CATCHALL`,
178
+ `SIMULATE_ORIGIN_PORT_OPEN`, `SIMULATE_EVENT_SECRECY_LEAK`.
179
+ - **cloudflared-config-lint** (local). Anchors AC-tunnel-hostnameRoutes.
180
+ `accountBound: false`. Runs `cloudflared tunnel --config <path> ingress validate`
181
+ via a local binary or the vendor container image
182
+ `cloudflare/cloudflared:2026.8.3`; refuses on `SIMULATE_INGRESS_INVALID`.
183
+ - **aud-presence-check** (local). Anchors AC-tunnel-accessGated,
184
+ AC-tunnel-accessGatedRefuse. `accountBound: false`. Reads the sidecar
185
+ and the paired manifests; asserts the shape flips correctly; refuses
186
+ on `SIMULATE_AUD_DROP` when the gated fixture drifts to open ingress.
187
+ - **real-account-connector-healthy** (account-bound). Anchors
188
+ AC-tunnel-connectorHealthy. `accountBound: true` on
189
+ `CI_HAS_CLOUDFLARE_ACCOUNT` AND `CI_HAS_HETZNER_ACCOUNT`. Without
190
+ both, records `accountBoundSkipped: true` and the aggregate flips to
191
+ `pass`.
192
+ - **real-account-tunnel-hostname-routes** (account-bound). Anchors
193
+ AC-tunnel-hostnameRoutes (public sub-case) and AC-tunnel-accessGated
194
+ (gated sub-case). `accountBound: true`. The public-hostname sub-case
195
+ runs on `CI_HAS_CLOUDFLARE_ACCOUNT` + `CI_HAS_HETZNER_ACCOUNT`; the
196
+ access-gated sub-case adds `CI_HAS_CLOUDFLARE_ACCESS` per the round-7
197
+ Q4 ratification. Sub-cases skip independently.
198
+
199
+ The delegate shims at
200
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/run-<probe>.mjs`
201
+ read the `SIMULATE_*` env vars and prepare mutated inputs in a scratch
202
+ dir; the probe modules never read a `SIMULATE_` env var and never
203
+ branch on being under mutation.
204
+
205
+ ## Two fixture variants times two hostname modes
206
+
207
+ The fixture lives under
208
+ `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/`:
209
+
210
+ - `compose-service/compose-fragment.yaml`: the cloudflared compose
211
+ service alongside the T-2 web and caddy services.
212
+ - `systemd-unit/cloudflared.service`: the systemd unit per the vendor
213
+ as-a-service docs.
214
+ - `<runtime>/cloudflare/tunnels/public-hostname.yaml`: manifest without
215
+ the AUD block.
216
+ - `<runtime>/cloudflare/tunnels/access-gated.yaml`: manifest with the
217
+ AUD block on every non-catch-all rule.
218
+ - `<runtime>/credentials/probe.json.example`: placeholder credentials
219
+ documenting the 0o400 mode discipline.
220
+ - `sidecars/access-gated.applied.json`: `appliedCapabilities` includes
221
+ `zeroTrustGate`.
222
+ - `sidecars/public-hostname.applied.json`: `appliedCapabilities`
223
+ excludes `zeroTrustGate`.
224
+
225
+ Reviewer boot (mocked, no account) from the fixture directory:
226
+
227
+ ```
228
+ cd packages/rcf-lite/test/fixtures/hetzner-throwaway-server
229
+ node ./run-tunnel-manifest-schema-validate.mjs && node ./run-cloudflared-config-lint.mjs && node ./run-aud-presence-check.mjs
230
+ ```
231
+
232
+ Reviewer boot (real account, throwaway server): set
233
+ `CI_HAS_CLOUDFLARE_ACCOUNT=true` and `CI_HAS_HETZNER_ACCOUNT=true`
234
+ (and `CI_HAS_CLOUDFLARE_ACCESS=true` for the gated sub-case) with the
235
+ account tokens loaded via `security-secrets-management`, then invoke the
236
+ two `run-real-account-*.mjs` scripts.
237
+
238
+ ## Composition on the shelf
239
+
240
+ Consumes:
241
+ - `platform-docker-compose-host` v1.0.0 (`containerHost` capability) for
242
+ the compose-service runtime path.
243
+ - `deploy-hetzner-server` v1.0.0 (`cloudHost` capability) for the bare
244
+ systemd-unit runtime path.
245
+ - `security-secrets-management` v1.0.1 (`secretsProvider` capability) for
246
+ the tunnel credentials `secretRef`.
247
+ - Optionally `edge-cloudflare-access` v1.0.0 (`zeroTrustGate` capability)
248
+ for the Access-gated shape.
249
+
250
+ Provides:
251
+ - `tunnelBridge` (declared under `capabilities` on `blueprint.json`).
252
+ - New global topic `edgeIngressBridge` on ADR-4001 (scope global).
253
+
254
+ Conflict-by-design with future non-Cloudflare siblings on the
255
+ `edgeIngressBridge` topic: Tailscale Funnel, Fly.io proxy, etc.
256
+ would contribute the same topic string with a different mechanism.
257
+ Round 7 ships only the Cloudflare Tunnel provider.