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,233 @@
1
+ {
2
+ "slug": "deploy-hetzner-server",
3
+ "version": "1.0.1",
4
+ "category": "deploy",
5
+ "capabilities": [
6
+ "cloudHost"
7
+ ],
8
+ "suggestedCompanions": [
9
+ {
10
+ "role": "logging",
11
+ "reason": "Every provisioner lifecycle event (provisionerReady, hetznerServerProvisioned, hetznerSnapshotTaken, hetznerServerDestroyed) writes through the applied logger; a logging companion supplies the factory."
12
+ },
13
+ {
14
+ "role": "errorHandling",
15
+ "reason": "An hcloud CLI non-zero exit, a rate-limited API call, a firewall-apply failure, a cloud-init timeout constructs an internal error record; an error-handling companion supplies the record factory and the boundary."
16
+ }
17
+ ],
18
+ "elicits": [
19
+ {
20
+ "id": "provisioning-tool",
21
+ "prompt": "Provisioning tool for Hetzner Cloud verbs: hcloud (default per ADR-3803, small self-documenting CLI with --output json) or raw-api (the provisioner fetches the Hetzner Cloud REST API directly, useful when the CI runner cannot install the binary).",
22
+ "kind": "enum",
23
+ "options": [
24
+ "hcloud",
25
+ "raw-api"
26
+ ],
27
+ "default": "hcloud"
28
+ },
29
+ {
30
+ "id": "server-type",
31
+ "prompt": "Hetzner Cloud shared-vCPU server type. cx23 is the cheapest shipped default; cx33 has more RAM and vCPU for a small compose stack.",
32
+ "kind": "enum",
33
+ "options": [
34
+ "cx23",
35
+ "cx33",
36
+ "cx43",
37
+ "cpx11",
38
+ "cpx21",
39
+ "cpx31",
40
+ "cpx41",
41
+ "cax11",
42
+ "cax21",
43
+ "cax31"
44
+ ],
45
+ "default": "cx23"
46
+ },
47
+ {
48
+ "id": "location",
49
+ "prompt": "Hetzner data centre. fsn1 (Falkenstein) is the shipped default per Baz ruling 2026-09-07 09:50Z on the Q2 ratification.",
50
+ "kind": "enum",
51
+ "options": [
52
+ "fsn1",
53
+ "nbg1",
54
+ "hel1",
55
+ "ash",
56
+ "hil"
57
+ ],
58
+ "default": "fsn1"
59
+ },
60
+ {
61
+ "id": "image",
62
+ "prompt": "Base image. ubuntu-24.04 is the shipped default; the six-block hardening baseline in the cloud-init template targets Debian and Ubuntu shapes.",
63
+ "kind": "string",
64
+ "default": "ubuntu-24.04"
65
+ },
66
+ {
67
+ "id": "ssh-source-prefixes",
68
+ "prompt": "Comma-separated CIDR list permitted on port 22 (operator-nominated set; the manifest schema refuses on 0.0.0.0/0). Populates firewallRules[ssh].sourceIps per TAC-3804.",
69
+ "kind": "string",
70
+ "default": "203.0.113.0/24"
71
+ },
72
+ {
73
+ "id": "firewall-id",
74
+ "prompt": "Reuse an existing Hetzner Cloud firewall id, or leave empty to have the provisioner create one from firewallRules.",
75
+ "kind": "string",
76
+ "default": ""
77
+ },
78
+ {
79
+ "id": "snapshot-cadence",
80
+ "prompt": "Snapshot cadence per ADR-3802. weekly is the recommended default; daily has higher recovery-point objective and cost; off skips the scheduled snapshot runner.",
81
+ "kind": "enum",
82
+ "options": [
83
+ "weekly",
84
+ "daily",
85
+ "off"
86
+ ],
87
+ "default": "weekly"
88
+ },
89
+ {
90
+ "id": "hetzner-managed-backups",
91
+ "prompt": "Opt-in to Hetzner-managed backups (paid, approximately 20 percent of monthly server price per ADR-3802).",
92
+ "kind": "enum",
93
+ "options": [
94
+ "true",
95
+ "false"
96
+ ],
97
+ "default": "false"
98
+ }
99
+ ],
100
+ "contributions": [
101
+ {
102
+ "id": "deploy-hetzner-server-REQ-001",
103
+ "kind": "req",
104
+ "path": "requirements/deploy-hetzner-server-req-001.json"
105
+ },
106
+ {
107
+ "id": "deploy-hetzner-server-REQ-002",
108
+ "kind": "req",
109
+ "path": "requirements/deploy-hetzner-server-req-002.json"
110
+ },
111
+ {
112
+ "id": "deploy-hetzner-server-REQ-003",
113
+ "kind": "req",
114
+ "path": "requirements/deploy-hetzner-server-req-003.json"
115
+ },
116
+ {
117
+ "id": "deploy-hetzner-server-REQ-004",
118
+ "kind": "req",
119
+ "path": "requirements/deploy-hetzner-server-req-004.json"
120
+ },
121
+ {
122
+ "id": "deploy-hetzner-server-REQ-005",
123
+ "kind": "req",
124
+ "path": "requirements/deploy-hetzner-server-req-005.json"
125
+ },
126
+ {
127
+ "id": "deploy-hetzner-server-REQ-006",
128
+ "kind": "req",
129
+ "path": "requirements/deploy-hetzner-server-req-006.json"
130
+ },
131
+ {
132
+ "id": "deploy-hetzner-server-US-37101",
133
+ "kind": "us",
134
+ "path": "user-stories/deploy-hetzner-server-us-37101.json"
135
+ },
136
+ {
137
+ "id": "deploy-hetzner-server-US-37102",
138
+ "kind": "us",
139
+ "path": "user-stories/deploy-hetzner-server-us-37102.json"
140
+ },
141
+ {
142
+ "id": "deploy-hetzner-server-US-37103",
143
+ "kind": "us",
144
+ "path": "user-stories/deploy-hetzner-server-us-37103.json"
145
+ },
146
+ {
147
+ "id": "deploy-hetzner-server-US-37104",
148
+ "kind": "us",
149
+ "path": "user-stories/deploy-hetzner-server-us-37104.json"
150
+ },
151
+ {
152
+ "id": "deploy-hetzner-server-US-37105",
153
+ "kind": "us",
154
+ "path": "user-stories/deploy-hetzner-server-us-37105.json"
155
+ },
156
+ {
157
+ "id": "deploy-hetzner-server-US-37106",
158
+ "kind": "us",
159
+ "path": "user-stories/deploy-hetzner-server-us-37106.json"
160
+ },
161
+ {
162
+ "id": "deploy-hetzner-server-US-37107",
163
+ "kind": "us",
164
+ "path": "user-stories/deploy-hetzner-server-us-37107.json"
165
+ },
166
+ {
167
+ "id": "deploy-hetzner-server-US-37108",
168
+ "kind": "us",
169
+ "path": "user-stories/deploy-hetzner-server-us-37108.json"
170
+ },
171
+ {
172
+ "id": "deploy-hetzner-server-US-37109",
173
+ "kind": "us",
174
+ "path": "user-stories/deploy-hetzner-server-us-37109.json"
175
+ },
176
+ {
177
+ "id": "TAC-3801-deploy-hetzner-server-provisioner",
178
+ "kind": "tac",
179
+ "path": "tacs/tac-3801-deploy-hetzner-server-provisioner.json"
180
+ },
181
+ {
182
+ "id": "TAC-3802-deploy-hetzner-server-manifest-schema",
183
+ "kind": "tac",
184
+ "path": "tacs/tac-3802-deploy-hetzner-server-manifest-schema.json"
185
+ },
186
+ {
187
+ "id": "TAC-3803-deploy-hetzner-server-cloud-init-template",
188
+ "kind": "tac",
189
+ "path": "tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json"
190
+ },
191
+ {
192
+ "id": "TAC-3804-deploy-hetzner-server-firewall-shape",
193
+ "kind": "tac",
194
+ "path": "tacs/tac-3804-deploy-hetzner-server-firewall-shape.json"
195
+ },
196
+ {
197
+ "id": "ADR-3801-deploy-hetzner-server-cloud-host-contract",
198
+ "kind": "adr",
199
+ "path": "adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json",
200
+ "scope": "global",
201
+ "topic": "linuxCloudHostContract",
202
+ "recommendedDefault": true,
203
+ "elicited": false,
204
+ "standardsTraceClause": "Hetzner Cloud API server resource and cloud-init user-data contract per hetzner-round-7-spec-2026-09-07.md"
205
+ },
206
+ {
207
+ "id": "ADR-3802-deploy-hetzner-server-snapshot-cadence",
208
+ "kind": "adr",
209
+ "path": "adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json",
210
+ "scope": "global",
211
+ "topic": "snapshotAndBackupCadence",
212
+ "recommendedDefault": true,
213
+ "elicited": true,
214
+ "standardsTraceClause": "Hetzner Cloud backups-and-snapshots documented pricing and mechanism per hetzner-round-7-spec-2026-09-07.md"
215
+ },
216
+ {
217
+ "id": "ADR-3803-deploy-hetzner-server-provisioning-tool",
218
+ "kind": "adr",
219
+ "path": "adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json",
220
+ "recommendedDefault": true,
221
+ "elicited": true,
222
+ "standardsTraceClause": "Hetzner Cloud CLI documented resource verbs per hetzner-round-7-spec-2026-09-07.md"
223
+ },
224
+ {
225
+ "id": "ADR-3804-deploy-hetzner-server-hardening-baseline",
226
+ "kind": "adr",
227
+ "path": "adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json",
228
+ "recommendedDefault": true,
229
+ "elicited": false,
230
+ "standardsTraceClause": "Debian unattended-upgrades wiki AND the estate baseline at operator/knowledge/systems/environments/hetzner-cloud/overview.md Security posture baseline per hetzner-round-7-spec-2026-09-07.md"
231
+ }
232
+ ]
233
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "adrId": "ADR-3801-deploy-hetzner-server-cloud-host-contract",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Cloud host contract: Hetzner Cloud VM as the shipped cloudHost shape (scope global, new topic linuxCloudHostContract)",
8
+ "context": "An applying project needs a first-class Linux VM contract that container-orchestration and edge-ingress consumers (round-7 T-2 platform-docker-compose-host, T-3 edge-cloudflare-tunnel) can compose on. Hetzner Cloud servers plus cloud-init user-data provide the v1.0.0 shape per https://docs.hetzner.cloud/ and https://docs.hetzner.com/cloud/servers/overview. Every applied cloud host needs one project-wide answer to the linuxCloudHostContract topic; a future non-Hetzner sibling (Vultr, DigitalOcean, Fly.io) would contribute the same topic string with a different provider, forcing a DELIBERATE conflict the operator resolves per the round-2 conflict pattern.",
9
+ "decision": "The shipped v1.0.0 default is a Hetzner Cloud server as the linuxCloudHostContract provider. The provisioner reads its shape from the applying manifest at hetzner/servers/name.json (TAC-3802) and creates a matching live server via hcloud CLI verbs (ADR-3803) or the raw Hetzner Cloud API. The linuxCloudHostContract topic scope is global on this blueprint; the topic string is linuxCloudHostContract (lower camelCase, one concept per topic, no version suffix, per the round-2 rules-for-new-topics clause on the shelf registry). Standards trace clause: Hetzner Cloud API server resource and cloud-init user-data contract per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "Consumer blueprints that run on a Linux host declare capabilities: [cloudHost] on their applied contract; the round-7 T-2 platform-docker-compose-host reads this capability at apply-time. Rebuild-from-manifest is always possible: the manifest is the source of truth, the live server is derivative. Drift from the shipped manifest to the live server is out of scope for v1.0.0; a later minor may add a drift-audit runner.",
11
+ "alternativesConsidered": [],
12
+ "createdAt": "2026-09-08T00:00:00.000Z",
13
+ "updatedAt": "2026-09-08T00:00:00.000Z"
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "adrId": "ADR-3802-deploy-hetzner-server-snapshot-cadence",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Snapshot cadence: weekly default, daily and off alternatives, Hetzner-managed backups elicited (scope global, new topic snapshotAndBackupCadence)",
8
+ "context": "An applying project needs a discipline for how often the Hetzner Cloud server takes a snapshot and whether Hetzner-managed backups are enabled. Hetzner documents snapshots as manual on-demand actions kept on cloud storage, and Hetzner-managed backups as a paid subscription at approximately 20 percent of monthly server price per https://docs.hetzner.com/cloud/servers/backups-snapshots/overview. Every cloud host needs one project-wide answer to the snapshotAndBackupCadence topic; a future sibling snapshot mechanism would contribute the same topic string with a different answer and force a DELIBERATE conflict.",
9
+ "decision": "The shipped v1.0.0 default is weekly snapshot cadence fired by a scheduled snapshot runner in the applying project. Alternatives elicited: daily (higher recovery-point objective, higher storage cost) and off (no automatic snapshot). Hetzner-managed backups are an elicited paid opt-in (approximately 20 percent monthly per the vendor docs); when opted-in, the scheduled snapshot runner still fires so the two mechanisms compose. The snapshotAndBackupCadence topic scope is global on this blueprint. recommendedDefault: true. Standards trace clause: Hetzner Cloud backups-and-snapshots documented pricing and mechanism per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "Weekly snapshot on a cx23 costs approximately EUR 0.005 per month in extra storage; daily is approximately EUR 0.035; Hetzner-managed backups add approximately EUR 0.80 per month on a cx23. The scheduled snapshot runner emits hetznerSnapshotTaken with snapshot id and wall-clock time so the observability sink surfaces the cadence.",
11
+ "alternativesConsidered": [],
12
+ "createdAt": "2026-09-08T00:00:00.000Z",
13
+ "updatedAt": "2026-09-08T00:00:00.000Z"
14
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "adrId": "ADR-3803-deploy-hetzner-server-provisioning-tool",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Provisioning tool: hcloud CLI default, raw Hetzner Cloud API alternative, Terraform rejected",
8
+ "context": "An applying project needs a documented answer to how the provisioner shells out to Hetzner Cloud. Options considered: hcloud CLI (small, self-documenting, JSON output on --output json, per https://github.com/hetznercloud/cli); the raw Hetzner Cloud REST API (no CLI dependency, useful when the target host cannot install a binary, per https://docs.hetzner.cloud/); Terraform (declarative, adds a second toolchain and a state file). Baz ruling 2026-09-07 09:50Z on decision 18 of the round-7 summary: hcloud not Terraform. Terraform adds a second toolchain and a state file that nobody in the estate needs.",
9
+ "decision": "The shipped v1.0.0 default is the hcloud CLI as the provisioning tool. Alternative elicited: raw-api (the provisioner fetches the Hetzner Cloud REST API directly). Terraform is a rejected alternative recorded in the blueprint README section 9 rejected-alternatives. elicited: true. recommendedDefault: hcloud. Standards trace clause: Hetzner Cloud CLI documented resource verbs per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "The applying project installs the hcloud binary on the operator laptop and every CI runner that provisions. The raw-api fork sits behind the same provisioner facade so no consumer code diverges. The provisioner-facade sole-reader guarantee holds on both paths.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "id": "terraform",
14
+ "summary": "Terraform declaring the Hetzner resources with the hetznercloud provider.",
15
+ "rejectedBecause": "Adds a second toolchain and a state file. The state file becomes a new source of truth to reconcile with git and with the live account. Baz ruling 2026-09-07 09:50Z."
16
+ }
17
+ ],
18
+ "createdAt": "2026-09-08T00:00:00.000Z",
19
+ "updatedAt": "2026-09-08T00:00:00.000Z"
20
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "adrId": "ADR-3804-deploy-hetzner-server-hardening-baseline",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Hardening baseline: SSH key-only, root disabled, UFW default-deny, DOCKER-USER chain, fail2ban, unattended-upgrades",
8
+ "context": "Every Hetzner server exposed to the public internet needs a minimum hardening set. Cloud-init landing the set at first boot is the operator-friendly shape: declarative, reproducible, kept in git per https://cloudinit.readthedocs.io/en/latest/. The Debian unattended-upgrades wiki (https://wiki.debian.org/UnattendedUpgrades) documents the auto-update mechanism the baseline turns on; the estate baseline at operator/knowledge/systems/environments/hetzner-cloud/overview.md Security posture baseline names the exact block set. The Hetzner community basic-cloud-config tutorial (https://community.hetzner.com/tutorials/basic-cloud-config) shows the vendor-recommended shape.",
9
+ "decision": "The shipped v1.0.0 hardening baseline is the six blocks: SSH key-only (PasswordAuthentication no), root disabled (PermitRootLogin no), UFW default-deny incoming with 22, 80 and 443 permitted per the manifest firewall shape, DOCKER-USER iptables chain wired to drop non-conforming egress, fail2ban SSH jail, unattended-upgrades on for security updates. The render-lint probe (cloud-init-render-lint.mjs) asserts every block appears in the rendered cloud-init YAML; the SIMULATE_HARDENING_DRIFT mutation removes a block and the probe FAILS naming the missing line. recommendedDefault: true. Standards trace clause: Debian unattended-upgrades wiki AND the estate baseline at operator/knowledge/systems/environments/hetzner-cloud/overview.md Security posture baseline per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "The applying project inherits the six-block baseline on every provisioned server. Deviation costs a fresh ADR at the applying project level. The baseline is text plus a rendered template; extension (extra ssh key, extra fail2ban jail) sits on top without rewriting.",
11
+ "alternativesConsidered": [],
12
+ "createdAt": "2026-09-08T00:00:00.000Z",
13
+ "updatedAt": "2026-09-08T00:00:00.000Z"
14
+ }
@@ -0,0 +1,71 @@
1
+ // Probe: cloud-init render lint.
2
+ //
3
+ // anchorAcId: AC-37104-1. accountBound: false.
4
+ //
5
+ // Renders the shipped cloud-init template against the fixture manifest at
6
+ // packages/rcf-lite/test/fixtures/hetzner-throwaway-server/hetzner/servers/ci-throwaway.json
7
+ // via the fixture's src/cloud-init-renderer.mjs. Asserts the six hardening
8
+ // baseline blocks appear in the rendered YAML.
9
+ //
10
+ // Purity: the probe reads only the shipped template and the fixture
11
+ // manifest; no process.env.SIMULATE_ switch is read here. The
12
+ // fixture-side renderer at src/cloud-init-renderer.mjs is the sole
13
+ // reader of SIMULATE_HARDENING_DRIFT and it mutates INPUT (the
14
+ // rendered YAML) only; the probe then FAILS naming the missing
15
+ // baseline block per hetzner-round-7-spec-2026-09-07.md section 3.4
16
+ // lesson 4. H-1 (2026-09-08).
17
+
18
+ import { readFile } from 'node:fs/promises';
19
+ import { resolve } from 'node:path';
20
+ import { FIXTURE_DIR } from './probe-utils.mjs';
21
+
22
+ export const anchorAcId = 'AC-37104-1';
23
+ export const accountBound = false;
24
+
25
+ export default async function runProbe() {
26
+ const rendererPath = resolve(FIXTURE_DIR, 'src/cloud-init-renderer.mjs');
27
+ const manifestPath = resolve(FIXTURE_DIR, 'hetzner/servers/ci-throwaway.json');
28
+ const { renderCloudInit, BASELINE_BLOCKS } = await import(rendererPath);
29
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
30
+ // In-process render only; pass explicit stub public-key material so
31
+ // the renderer never shells to hcloud (mock path stays hermetic).
32
+ const rendered = await renderCloudInit(manifest, {
33
+ publicKeys: ['ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFakePubKeyForRenderLintHardeningH1 rcf-lite-ci-mock'],
34
+ });
35
+ const missing = [];
36
+ const present = [];
37
+ for (const block of BASELINE_BLOCKS) {
38
+ const ok = block.markers.every((m) => rendered.includes(m));
39
+ if (ok) {
40
+ present.push(block.id);
41
+ } else {
42
+ const missingMarkers = block.markers.filter((m) => !rendered.includes(m));
43
+ missing.push({ id: block.id, label: block.label, missingMarkers });
44
+ }
45
+ }
46
+ const results = [];
47
+ if (missing.length > 0) {
48
+ for (const m of missing) {
49
+ results.push({
50
+ anchorAcId,
51
+ verdict: 'fail',
52
+ detail: `cloud-init hardening baseline block "${m.label}" is missing from the rendered YAML (missing markers: ${m.missingMarkers.join(', ')}).`,
53
+ });
54
+ }
55
+ } else {
56
+ results.push({
57
+ anchorAcId,
58
+ verdict: 'pass',
59
+ detail: `all ${BASELINE_BLOCKS.length} baseline blocks present in the rendered YAML: ${present.join(', ')}.`,
60
+ });
61
+ }
62
+ return {
63
+ results,
64
+ extra: {
65
+ manifestName: manifest.name,
66
+ renderedByteLength: rendered.length,
67
+ presentBlocks: present,
68
+ missingBlocks: missing.map((m) => m.id),
69
+ },
70
+ };
71
+ }
@@ -0,0 +1,164 @@
1
+ // Probe: hcloud dry-run mock (v1.0.1 mutation-purified per H-1).
2
+ //
3
+ // anchorAcId: AC-37101-1 (provisioner facade sole reader + provisionerReady
4
+ // on boot; also covers AC-37109-1 event-secrecy across the lifecycle;
5
+ // also covers AC-14501-1 mock-consumes-rendered-file assertions per H-1
6
+ // hardening block REQ-145).
7
+ // accountBound: false.
8
+ //
9
+ // Drives the provisioner facade with the fixture's ci-throwaway manifest
10
+ // against the mocked hcloud shim (src/hcloud-mock.mjs) and consumes the
11
+ // SAME rendered cloud-init user-data file the real path consumes (the
12
+ // fixture renderer writes it under hetzner/servers/rendered/ before the
13
+ // facade is exercised, and this probe reads it back and asserts an
14
+ // ssh-ed25519/ssh-rsa public-key line under the deploy user plus a
15
+ // NOPASSWD directive naming that user). The rendered-file assertion is
16
+ // what stops a mocked probe from passing while the real path fails on
17
+ // the same artefact (H-1 REQ-145 / AC-14501-1).
18
+ //
19
+ // The probe body reads NO process.env.SIMULATE_ switch. Fixture-side
20
+ // mutations live in src/cloud-init-renderer.mjs, src/hcloud-mock.mjs
21
+ // and src/provisioner-facade.mjs and alter INPUT only.
22
+ //
23
+ // Assertions:
24
+ // - the facade opens on ready() and fires provisionerReady with a
25
+ // metadata-only payload {tool, apiHost}.
26
+ // - createServer parses the mocked JSON stdout and emits
27
+ // hetznerServerProvisioned with id, primaryIpv4, location=fsn1,
28
+ // serverType=cx23.
29
+ // - takeSnapshot fires hetznerSnapshotTaken with snapshot id and time.
30
+ // - destroyServer fires hetznerServerDestroyed with the id.
31
+ // - no event body across the run carries the token, an ssh private-key
32
+ // marker, or the rendered user-data bytes (event-secrecy check).
33
+ // - the rendered cloud-init file exists under the fixture's rendered
34
+ // directory and carries at least one ssh-ed25519 or ssh-rsa
35
+ // authorized-keys line under the deploy user plus a NOPASSWD
36
+ // directive naming that user (H-1 REQ-145 / AC-14501-1).
37
+
38
+ import { readFile, access } from 'node:fs/promises';
39
+ import { relative, resolve } from 'node:path';
40
+ import { FIXTURE_DIR, PROJECT_ROOT } from './probe-utils.mjs';
41
+
42
+ export const anchorAcId = 'AC-37101-1';
43
+ export const accountBound = false;
44
+
45
+ const TOKEN = 'test-fixture-token-000000000000000000000000000000000000000000';
46
+ const USER_DATA_MARKER = 'unattended-upgrades on for security updates';
47
+ const PRIVATE_KEY_MARKER = 'BEGIN OPENSSH PRIVATE KEY';
48
+
49
+ export default async function runProbe() {
50
+ const facadePath = resolve(FIXTURE_DIR, 'src/provisioner-facade.mjs');
51
+ const rendererPath = resolve(FIXTURE_DIR, 'src/cloud-init-renderer.mjs');
52
+ const manifestPath = resolve(FIXTURE_DIR, 'hetzner/servers/ci-throwaway.json');
53
+ const { createProvisionerFacade, createEventLog } = await import(facadePath);
54
+ const { renderCloudInitToFile } = await import(rendererPath);
55
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
56
+ const results = [];
57
+
58
+ // Render the cloud-init to disk with a fixture-side stubbed public
59
+ // key (the mock path never calls hcloud ssh-key describe); the real
60
+ // path resolves the material via hcloud and writes to the same
61
+ // location. Both consume the same file shape.
62
+ const publicKeys = ['ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFakePubKeyForMockedRunHardeningH1 rcf-lite-ci-mock'];
63
+ const { renderedPath, rendered } = await renderCloudInitToFile(manifest, { publicKeys });
64
+
65
+ // H-1 REQ-145 / AC-14501-1: assert the rendered file carries an
66
+ // ssh-ed25519 or ssh-rsa line for the deploy user AND a NOPASSWD
67
+ // directive naming that user. This assertion is what breaks the
68
+ // "mock passes while real path fails on the same artefact" class.
69
+ const renderedFromDisk = await readFile(renderedPath, 'utf8');
70
+ const sshKeyLine = /(ssh-ed25519|ssh-rsa)\s+/i.test(renderedFromDisk);
71
+ const nopasswdLine = /deploy\s+ALL\s*=\s*\(ALL\)\s+NOPASSWD/i.test(renderedFromDisk);
72
+ if (!sshKeyLine) {
73
+ results.push({
74
+ anchorAcId: 'AC-14501-1',
75
+ verdict: 'fail',
76
+ detail: `rendered cloud-init at ${relative(PROJECT_ROOT, renderedPath)} carries no ssh-ed25519 or ssh-rsa authorized-keys line for the deploy user; the real path would provision a server with an unusable authorized_keys entry.`,
77
+ });
78
+ } else if (!nopasswdLine) {
79
+ results.push({
80
+ anchorAcId: 'AC-14501-1',
81
+ verdict: 'fail',
82
+ detail: `rendered cloud-init at ${relative(PROJECT_ROOT, renderedPath)} carries no NOPASSWD directive for the deploy user; the six sudoed baseline checks would block on a tty prompt.`,
83
+ });
84
+ } else {
85
+ results.push({
86
+ anchorAcId: 'AC-14501-1',
87
+ verdict: 'pass',
88
+ detail: `rendered cloud-init at ${relative(PROJECT_ROOT, renderedPath)} carries an ssh public-key line for the deploy user (byteLength ${rendered.length}) and a NOPASSWD sudoers directive for that user; the mock and the real path consume the same artefact.`,
89
+ });
90
+ }
91
+
92
+ const { events, eventSink } = createEventLog();
93
+ const facade = createProvisionerFacade({
94
+ token: TOKEN,
95
+ tool: 'hcloud',
96
+ apiHost: 'https://api.hetzner.cloud/v1',
97
+ eventSink,
98
+ });
99
+ try {
100
+ await facade.ready();
101
+ const server = await facade.createServer(manifest);
102
+ const snapshot = await facade.takeSnapshot({ id: server.id, name: manifest.name }, `${manifest.name}-${new Date().toISOString()}`);
103
+ await facade.destroyServer(server);
104
+ const provisionerReady = events.find((e) => e.event === 'provisionerReady');
105
+ const provisioned = events.find((e) => e.event === 'hetznerServerProvisioned');
106
+ const snapped = events.find((e) => e.event === 'hetznerSnapshotTaken');
107
+ const destroyed = events.find((e) => e.event === 'hetznerServerDestroyed');
108
+ if (!provisionerReady) {
109
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'fail', detail: 'provisionerReady event did not fire on ready().' });
110
+ } else if (provisionerReady.tool !== 'hcloud' || provisionerReady.apiHost !== 'https://api.hetzner.cloud/v1') {
111
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'fail', detail: `provisionerReady payload shape wrong: ${JSON.stringify(provisionerReady)}` });
112
+ } else {
113
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'pass', detail: 'provisionerReady fired with metadata-only {tool, apiHost} payload.' });
114
+ }
115
+ if (!provisioned || !provisioned.id || !provisioned.primaryIpv4 || provisioned.location !== 'fsn1' || provisioned.serverType !== 'cx23') {
116
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'fail', detail: `hetznerServerProvisioned event missing or malformed: ${JSON.stringify(provisioned)}` });
117
+ } else {
118
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'pass', detail: `hetznerServerProvisioned fired with id=${provisioned.id} primaryIpv4=${provisioned.primaryIpv4} location=fsn1 serverType=cx23.` });
119
+ }
120
+ if (!snapped || !snapped.snapshotId || !snapped.wallClockTime) {
121
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'fail', detail: `hetznerSnapshotTaken event missing or malformed: ${JSON.stringify(snapped)}` });
122
+ }
123
+ if (!destroyed || !destroyed.id) {
124
+ results.push({ anchorAcId: 'AC-37101-1', verdict: 'fail', detail: `hetznerServerDestroyed event missing or malformed: ${JSON.stringify(destroyed)}` });
125
+ }
126
+ // Event-secrecy scan across every event body.
127
+ const leaks = [];
128
+ for (const e of events) {
129
+ const body = JSON.stringify(e);
130
+ if (body.includes(TOKEN)) leaks.push({ event: e.event, marker: 'HETZNER_ACCOUNT_API_KEY value' });
131
+ if (body.includes(PRIVATE_KEY_MARKER)) leaks.push({ event: e.event, marker: 'ssh private key' });
132
+ if (body.includes(USER_DATA_MARKER)) leaks.push({ event: e.event, marker: 'rendered user-data bytes' });
133
+ }
134
+ if (leaks.length > 0) {
135
+ results.push({
136
+ anchorAcId: 'AC-37109-1',
137
+ verdict: 'fail',
138
+ detail: `event-secrecy leak: ${leaks.map((l) => `${l.event} carries ${l.marker}`).join('; ')} (defensive-fake finding; check for a SIMULATE_EVENT_SECRECY_LEAK mutation in the fixture-side facade shim).`,
139
+ });
140
+ } else {
141
+ results.push({
142
+ anchorAcId: 'AC-37109-1',
143
+ verdict: 'pass',
144
+ detail: `event-secrecy scan across ${events.length} event bodies found no leak of the token, ssh private key, or user-data content.`,
145
+ });
146
+ }
147
+ } catch (err) {
148
+ results.push({
149
+ anchorAcId: 'AC-37101-1',
150
+ verdict: 'fail',
151
+ detail: `provisioner facade lifecycle threw: ${err.message} (probable cause: mocked hcloud stdout is not JSON; check for a SIMULATE_JSON_PARSE_STRIP mutation in the fixture-side mock shim).`,
152
+ });
153
+ }
154
+ return {
155
+ results,
156
+ extra: {
157
+ eventCount: events.length,
158
+ events: events.map((e) => ({ event: e.event, keys: Object.keys(e).sort() })),
159
+ renderedPath: relative(PROJECT_ROOT, renderedPath),
160
+ renderedByteLength: rendered.length,
161
+ renderedAssertions: { sshKeyLine, nopasswdLine },
162
+ },
163
+ };
164
+ }