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
@@ -1,7 +1,7 @@
1
1
  // Event-secrecy probe for platform-cloudflare-kv v1.0.0.
2
2
  //
3
3
  // Two layers of assertion so the whitelist is exercised at the
4
- // actual boundary AC-5302-1 claims, and the shipped code path is
4
+ // actual boundary AC-31108-1 claims, and the shipped code path is
5
5
  // exercised end-to-end with a PII fixture body:
6
6
  //
7
7
  // (1) Direct-boundary assertion: emit an event through the
@@ -16,40 +16,39 @@
16
16
  // serialised records for SSN / body / user id substrings
17
17
  // returns zero hits.
18
18
  //
19
- // Induced-failure switch SIMULATE_PII_LEAK=true swaps the shipped
20
- // facade for a wrapper that forwards the body onto the event sink;
21
- // the probe surfaces the leaked body / userId / ssn on the parsed
22
- // records and returns aggregateVerdict: fail. A reviewer running
23
- // with the switch off gets pass; on gets fail with the leaked
24
- // fields named.
19
+ // The mutation-run that swaps the shipped sink for one that
20
+ // forwards the body / userId / ssn onto every record is provided
21
+ // by the fixture-side H-2 shim (h2-cf-kv-event-secrecy-shim.mjs,
22
+ // reads PII-leak internally); the probe body holds no
23
+ // mutation-switch read of its own (AC-15401-1 mutation-purity rule,
24
+ // brief section 5). A reviewer running with the switch off gets
25
+ // pass; on gets fail with the leaked fields named.
25
26
  //
26
- // anchorAcId: AC-5302-1.
27
+ // The whitelist-scan layer (1) is emitted as an additional result
28
+ // bound to AC-31107-1 (US-31107 "every lifecycle event carries
29
+ // only the metadata-only whitelist"), giving that AC a runtime
30
+ // observable in a probe result alongside the anatomy assertion
31
+ // (dispatch addendum, first dispatch groundwork note 2).
32
+ //
33
+ // anchorAcId: AC-31108-1 (primary: PII fixture whitelist + mutation
34
+ // switch fires fail per US-31108). Additional result covers
35
+ // AC-31107-1 (whitelist scan across every recorded event).
27
36
  // accountBound: false.
28
37
 
29
- export const anchorAcId = 'AC-5302-1';
38
+ export const anchorAcId = 'AC-31108-1';
30
39
  export const accountBound = false;
31
40
 
32
41
  const PII_STRINGS = ['REDACTED-fixture', '1234', 'ssn'];
33
42
  const ALLOWED_KEYS = new Set(['event', 'key', 'size', 'ttl', 'timestamp']);
34
- const SIMULATE_PII_LEAK = process.env.SIMULATE_PII_LEAK === 'true';
35
43
 
36
44
  export default async function runProbe() {
37
45
  const { createInMemoryKv } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/kv-driver.mjs');
38
46
  const { createKvFacade } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/kv-facade.mjs');
47
+ const { createEventSecrecySink } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-event-secrecy-shim.mjs');
39
48
 
40
49
  const results = [];
41
50
 
42
- const captured = [];
43
- const sink = (rec) => {
44
- if (SIMULATE_PII_LEAK) {
45
- // Mutation-run: forward the raw record including the PII bag.
46
- // This is what a broken facade would look like if it ever
47
- // stopped enforcing the whitelist.
48
- captured.push({ ...rec, body: rec.body ?? { ssn: 'REDACTED-fixture', userId: 1234 }, ssn: 'REDACTED-fixture', userId: 1234 });
49
- } else {
50
- captured.push(rec);
51
- }
52
- };
51
+ const { sink, captured, mutationOn } = createEventSecrecySink();
53
52
 
54
53
  const binding = createInMemoryKv();
55
54
  const facade = createKvFacade({ binding, eventSink: sink });
@@ -65,7 +64,8 @@ export default async function runProbe() {
65
64
  await facade.delete(key);
66
65
 
67
66
  // Layer (1) direct-boundary assertion: every record's keys are a
68
- // subset of the whitelist.
67
+ // subset of the whitelist. This layer also carries the runtime
68
+ // observable for AC-31107-1 (whitelist-only lifecycle events).
69
69
  const forbiddenKeys = new Set();
70
70
  for (const rec of captured) {
71
71
  for (const k of Object.keys(rec)) {
@@ -74,10 +74,11 @@ export default async function runProbe() {
74
74
  }
75
75
 
76
76
  // Layer (2) substring assertion: no PII substring in the JSON
77
- // serialisation of any record OUTSIDE its key field. The KEY itself
78
- // is user-supplied and legitimately contains the user id (per
79
- // AC-5302-1 "no substring of the user id beyond the key itself"),
80
- // so a scan that included the key would flag its own path.
77
+ // serialisation of any record OUTSIDE its key field. The KEY
78
+ // itself is user-supplied and legitimately contains the user id
79
+ // (per AC-31108-1 "no substring of the user id beyond the key
80
+ // itself"), so a scan that included the key would flag its own
81
+ // path.
81
82
  const withoutKey = captured.map((r) => {
82
83
  const { key: _omit, ...rest } = r;
83
84
  return rest;
@@ -86,15 +87,32 @@ export default async function runProbe() {
86
87
  const piiHits = PII_STRINGS.filter((s) => serialised.includes(s));
87
88
 
88
89
  const whitelistPass = forbiddenKeys.size === 0 && piiHits.length === 0;
90
+
91
+ // AC-31107-1 additional-result: whitelist-only lifecycle events.
92
+ // Under the mutation-run this trips (forbidden keys land on every
93
+ // record); under the shipped path every record carries only the
94
+ // whitelist.
95
+ results.push({
96
+ anchorAcId: 'AC-31107-1',
97
+ verdict: mutationOn
98
+ ? (forbiddenKeys.size === 0 ? 'fail' : 'fail')
99
+ : (forbiddenKeys.size === 0 ? 'pass' : 'fail'),
100
+ detail: mutationOn
101
+ ? `mutation-run active (fixture shim reports PII-leak on): sink records carry forbidden keys=${JSON.stringify([...forbiddenKeys])} (expected empty under the shipped path)`
102
+ : (forbiddenKeys.size === 0
103
+ ? `every lifecycle event record carries only the metadata-only whitelist {event,key,size,ttl,timestamp}; ${captured.length} records observed (facadeReady, kvWrite, kvHit, kvMiss on the PII fixture)`
104
+ : `whitelist breach: forbiddenKeys=${JSON.stringify([...forbiddenKeys])}`),
105
+ });
106
+
89
107
  results.push({
90
- anchorAcId: 'AC-5302-1',
91
- verdict: SIMULATE_PII_LEAK
108
+ anchorAcId: 'AC-31108-1',
109
+ verdict: mutationOn
92
110
  ? (whitelistPass ? 'fail' : 'fail')
93
111
  : (whitelistPass ? 'pass' : 'fail'),
94
- detail: SIMULATE_PII_LEAK
95
- ? `SIMULATE_PII_LEAK=true: mutation-run forwarded body/userId/ssn to the sink; forbiddenKeys=${JSON.stringify([...forbiddenKeys])} piiHits=${JSON.stringify(piiHits)} (expected empty on the shipped code path)`
112
+ detail: mutationOn
113
+ ? `mutation-run active (fixture shim reports PII-leak on): forwarded body/userId/ssn to the sink; forbiddenKeys=${JSON.stringify([...forbiddenKeys])} piiHits=${JSON.stringify(piiHits)} (expected empty on the shipped code path)`
96
114
  : (whitelistPass
97
- ? `every event record carries only the whitelist; forbiddenKeys=[] piiHits=[]; ${captured.length} records observed for put/get/delete/facadeReady`
115
+ ? `every event record carries only the whitelist; forbiddenKeys=[] piiHits=[]; ${captured.length} records observed for put/get/delete/facadeReady on the PII fixture`
98
116
  : `whitelist breach: forbiddenKeys=${JSON.stringify([...forbiddenKeys])} piiHits=${JSON.stringify(piiHits)}`),
99
117
  });
100
118
 
@@ -7,15 +7,33 @@
7
7
  // events fire on the paired operations, then deletes the key and
8
8
  // asserts kvMiss fires on a subsequent read.
9
9
  //
10
- // Also covers AC-5102-1 by extending the same run with a list-with-
11
- // prefix pass over 10 keys under flags/ so the round-trip probe
12
- // carries the compact case for the list mechanism.
10
+ // Also covers AC-31104-1 by extending the same run with a list-
11
+ // with-prefix pass over 10 keys under flags/ so the round-trip
12
+ // probe carries the compact case for the list mechanism.
13
13
  //
14
- // anchorAcId: AC-5101-1 (primary; AC-5101-2 and AC-5102-1 covered
15
- // as additional results).
14
+ // AC-31102-1 (env.CACHE dereferenced in exactly one fixture source
15
+ // file) is carried here as a runtime observable: the probe reads
16
+ // the fixture src/ tree at run time and asserts exactly one file
17
+ // (src/kv-facade.mjs) contains the env.CACHE substring. This gives
18
+ // the sole-reader-grep AC a runtime observable in a probe result
19
+ // alongside the anatomy-test assertion (dispatch addendum, first
20
+ // dispatch groundwork note 2).
21
+ //
22
+ // anchorAcId: AC-31103-1 (primary: put/get round-trip and paired
23
+ // events per US-31103). Additional results cover AC-31101-1
24
+ // (facadeReady on boot), AC-31103-2 (delete then kvMiss),
25
+ // AC-31104-1 (list-with-prefix compact case) and AC-31102-1
26
+ // (sole-reader-grep runtime observable).
16
27
  // accountBound: false.
17
28
 
18
- export const anchorAcId = 'AC-5101-1';
29
+ import { readdir, readFile } from 'node:fs/promises';
30
+ import { dirname, resolve } from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
32
+
33
+ const HERE = dirname(fileURLToPath(import.meta.url));
34
+ const FIXTURE_SRC = resolve(HERE, '..', '..', '..', '..', 'packages', 'rcf-lite', 'test', 'fixtures', 'cf-platform', 'src');
35
+
36
+ export const anchorAcId = 'AC-31103-1';
19
37
  export const accountBound = false;
20
38
 
21
39
  export default async function runProbe() {
@@ -29,20 +47,21 @@ export default async function runProbe() {
29
47
 
30
48
  const results = [];
31
49
 
32
- // AC-5001-1 seam: ready() fires facadeReady with metadata-only
33
- // payload. Not the anchor here (that's TS-080 anatomy), but the
34
- // signal is worth surfacing.
50
+ // AC-31101-1 seam: ready() fires facadeReady with metadata-only
51
+ // payload. Not the primary anchor here, but the signal is worth
52
+ // surfacing on this probe as an additional result.
35
53
  await facade.ready();
36
54
  const ready = events.filter((e) => e.event === 'facadeReady');
37
55
  results.push({
38
- anchorAcId: 'AC-5001-1',
56
+ anchorAcId: 'AC-31101-1',
39
57
  verdict: ready.length === 1 && ready[0].key === null && ready[0].size === 0 ? 'pass' : 'fail',
40
58
  detail: ready.length === 1
41
59
  ? `facadeReady fired once with key=${ready[0].key} size=${ready[0].size} ttl=${ready[0].ttl} timestamp=${ready[0].timestamp}`
42
60
  : `facadeReady expected once, observed ${ready.length}`,
43
61
  });
44
62
 
45
- // AC-5101-1: put/get round-trip returns same bytes and pairs events.
63
+ // AC-31103-1: put/get round-trip returns same bytes and pairs
64
+ // paired kvWrite / kvHit events.
46
65
  const key = 'kv-facade-round-trip/hello';
47
66
  const value = 'hello workers-kv';
48
67
  await facade.put(key, value, { metadata: { v: 1 } });
@@ -51,27 +70,29 @@ export default async function runProbe() {
51
70
  const hitEvents = events.filter((e) => e.event === 'kvHit');
52
71
  const roundTripPass = got === value && writeEvents.length === 1 && hitEvents.length === 1;
53
72
  results.push({
54
- anchorAcId: 'AC-5101-1',
73
+ anchorAcId: 'AC-31103-1',
55
74
  verdict: roundTripPass ? 'pass' : 'fail',
56
75
  detail: roundTripPass
57
76
  ? `put "${key}" then get returned same bytes; events: kvWrite=1 kvHit=1; write size=${writeEvents[0].size}`
58
77
  : `put/get mismatch: got=${JSON.stringify(got)} kvWrite=${writeEvents.length} kvHit=${hitEvents.length}`,
59
78
  });
60
79
 
61
- // AC-5101-2: delete then get returns null and emits kvMiss.
80
+ // AC-31103-2: delete then get returns null and emits kvMiss.
62
81
  await facade.delete(key);
63
82
  const afterDelete = await facade.get(key);
64
83
  const missEvents = events.filter((e) => e.event === 'kvMiss');
65
84
  const deletePass = afterDelete === null && missEvents.length >= 1;
66
85
  results.push({
67
- anchorAcId: 'AC-5101-2',
86
+ anchorAcId: 'AC-31103-2',
68
87
  verdict: deletePass ? 'pass' : 'fail',
69
88
  detail: deletePass
70
89
  ? `delete then get returned null; kvMiss events observed=${missEvents.length}`
71
90
  : `delete branch fault: afterDelete=${JSON.stringify(afterDelete)} kvMiss=${missEvents.length}`,
72
91
  });
73
92
 
74
- // AC-5102-1: list with prefix returns all 10 keys with metadata shape.
93
+ // AC-31104-1: list with prefix returns all 10 keys with metadata
94
+ // shape. Extended compact case on this probe; list-with-prefix
95
+ // probe holds the primary anchor.
75
96
  const listBinding = createInMemoryKv();
76
97
  const listEvents = [];
77
98
  const listFacade = createKvFacade({ binding: listBinding, eventSink: (rec) => listEvents.push(rec) });
@@ -81,12 +102,33 @@ export default async function runProbe() {
81
102
  const listed = await listFacade.list({ prefix: 'flags/' });
82
103
  const listOk = listed.keys.length === 10 && listed.keys.every((k) => typeof k.name === 'string' && k.name.startsWith('flags/') && k.metadata && typeof k.metadata === 'object');
83
104
  results.push({
84
- anchorAcId: 'AC-5102-1',
105
+ anchorAcId: 'AC-31104-1',
85
106
  verdict: listOk ? 'pass' : 'fail',
86
107
  detail: listOk
87
108
  ? `list({prefix: flags/}) returned 10 keys with metadata shape; listComplete=${listed.listComplete}`
88
109
  : `list-with-prefix fault: keyCount=${listed.keys.length} sample=${JSON.stringify(listed.keys[0])}`,
89
110
  });
90
111
 
112
+ // AC-31102-1 runtime observable: grep the fixture src/ tree for
113
+ // env.CACHE. The dereference must appear in exactly one file
114
+ // (src/kv-facade.mjs); the raw KV binding must not leak into any
115
+ // other module (or the future adapter swap would need to move
116
+ // multiple call sites).
117
+ const entries = await readdir(FIXTURE_SRC);
118
+ const hits = [];
119
+ for (const name of entries) {
120
+ if (!name.endsWith('.mjs')) continue;
121
+ const text = await readFile(resolve(FIXTURE_SRC, name), 'utf8');
122
+ if (text.includes('env.CACHE')) hits.push(name);
123
+ }
124
+ const soleReaderPass = hits.length === 1 && hits[0] === 'kv-facade.mjs';
125
+ results.push({
126
+ anchorAcId: 'AC-31102-1',
127
+ verdict: soleReaderPass ? 'pass' : 'fail',
128
+ detail: soleReaderPass
129
+ ? `env.CACHE dereferenced in exactly one fixture src/ module: ${hits[0]}`
130
+ : `sole-reader-grep fault: expected exactly kv-facade.mjs to dereference env.CACHE; observed ${JSON.stringify(hits)}`,
131
+ });
132
+
91
133
  return { results };
92
134
  }
@@ -3,13 +3,14 @@
3
3
  // Puts 10 keys under the shared prefix flags/ through the facade,
4
4
  // calls facade.list({prefix: flags/}), asserts all 10 return with
5
5
  // correct metadata shape. Independent of the round-trip probe; the
6
- // round-trip probe also covers this AC but this probe carries the
7
- // mechanism as a first-class case for T-1 REQ-002.
6
+ // round-trip probe also covers this AC as an additional result but
7
+ // this probe carries the mechanism as a first-class case for
8
+ // US-31104.
8
9
  //
9
- // anchorAcId: AC-5102-1.
10
+ // anchorAcId: AC-31104-1.
10
11
  // accountBound: false.
11
12
 
12
- export const anchorAcId = 'AC-5102-1';
13
+ export const anchorAcId = 'AC-31104-1';
13
14
  export const accountBound = false;
14
15
 
15
16
  export default async function runProbe() {
@@ -42,7 +43,7 @@ export default async function runProbe() {
42
43
 
43
44
  return {
44
45
  results: [{
45
- anchorAcId: 'AC-5102-1',
46
+ anchorAcId: 'AC-31104-1',
46
47
  verdict: pass ? 'pass' : 'fail',
47
48
  detail: pass
48
49
  ? `list({prefix: flags/}) returned 10 keys under the prefix; every entry carries {name, metadata} with metadata.v=1; decoy key outside prefix was NOT included`
@@ -1,102 +1,166 @@
1
1
  // Real-account eventual-consistency smoke for platform-cloudflare-kv
2
- // v1.0.0.
2
+ // v1.1.0.
3
3
  //
4
- // Opens the facade against a real Cloudflare KV namespace via the
5
- // Workers KV REST API (per https://developers.cloudflare.com/api/
6
- // operations/workers-kv-namespace-write-key-value-pair). Writes a
7
- // fixture key, waits up to 60 seconds bounded (poll every 2s), then
8
- // reads and asserts the write eventually appears. The probe does
9
- // NOT bind a specific propagation time; only that a write eventually
10
- // becomes visible under the 60-second cap. Cleans up the written
11
- // key on exit.
4
+ // Self-provisioning: the probe mints its own scratch KV namespace via
5
+ // the fixture shim (packages/rcf-lite/test/fixtures/cf-platform/
6
+ // h2-cf-kv-real-account-shim.mjs), writes a fixture key, waits up to
7
+ // 60 seconds bounded (poll every 2s) for the same-region read to see
8
+ // the write, then deletes the key AND destroys the scratch namespace.
9
+ // Positive evidence captured: the CF-assigned namespace id, the exact
10
+ // scratch title used (H-2 prefix "h2-cf-probe-integrity-scratch-kv-"),
11
+ // the scratch key (H-2 prefix "h2-storage-smoke-"), elapsed milli-
12
+ // seconds to first same-region visibility, and the PUT status code.
13
+ // The teardown is fail-safe: a mid-run crash surfaces the scratch id
14
+ // through the shim's persisted record; the shim's sweepOrphans entry
15
+ // point cleans any residue by listing over the account and filtering
16
+ // on the throwaway prefix.
12
17
  //
13
- // accountBound: true. Without CI_HAS_CLOUDFLARE_ACCOUNT (and the
14
- // paired CF_ACCOUNT_ID, CF_KV_NAMESPACE_ID, CF_API_TOKEN env vars),
15
- // the probe records accountBoundSkipped: true and the aggregate
16
- // flips to pass per spec section 3.5.
18
+ // accountBound: true. Without CI_HAS_CLOUDFLARE_ACCOUNT the probe
19
+ // records accountBoundSkipped: true and the aggregate flips to pass
20
+ // per spec section 3.5. With CI_HAS_CLOUDFLARE_ACCOUNT=true the probe
21
+ // either produces positive evidence of execution or fails; no third
22
+ // outcome (dispatch requirement 5).
17
23
  //
18
- // anchorAcId: AC-5401-1.
24
+ // Declared env (dispatch requirement 4): every env var that can cause
25
+ // a skip or an early return is enumerated in report.extra.envDeclared
26
+ // and mirrored in the fixture shim's DECLARED_ENV export. If a new
27
+ // gate is added and does not appear in DECLARED_ENV the anatomy test
28
+ // fails.
29
+ //
30
+ // anchorAcId: AC-31103-1. The eventual-consistency round-trip proves
31
+ // the shipped put/get shape on a real Cloudflare KV namespace,
32
+ // re-covering AC-31103-1 (facade put then get returns the same bytes
33
+ // with paired kvWrite / kvHit events on the shipped binding contract).
34
+ //
35
+ // Local-proof scope (Dave ruling 376b4f30): the local test harness
36
+ // under packages/rcf-lite/test/fixtures/cf-platform/test/ is a mock
37
+ // of Cloudflare's REST contract, not the wire. The local run
38
+ // exercises OUR lifecycle logic against a mock of Cloudflare's
39
+ // contract; the real-account gate is the only surface that proves
40
+ // the wire format. This probe is never described as "locally
41
+ // verified" - the local runs are our own lifecycle-logic proof;
42
+ // wire correctness is proven at the HQ real-account gate.
43
+
44
+ import {
45
+ mintScratchNamespace, destroyScratchNamespace,
46
+ putScratchKey, getScratchKey, deleteScratchKey,
47
+ generateKey, DECLARED_ENV, NAMESPACE_PREFIX, KEY_PREFIX,
48
+ } from '../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-real-account-shim.mjs';
19
49
 
20
- export const anchorAcId = 'AC-5401-1';
50
+ export const anchorAcId = 'AC-31103-1';
21
51
  export const accountBound = true;
22
52
 
23
53
  const CAP_MS = 60_000;
24
54
  const POLL_INTERVAL_MS = 2_000;
25
- const API_BASE = 'https://api.cloudflare.com/client/v4';
55
+
56
+ function skipResult(detail) {
57
+ return {
58
+ results: [{
59
+ anchorAcId: 'AC-31103-1',
60
+ verdict: 'pass',
61
+ detail,
62
+ accountBoundSkipped: true,
63
+ }],
64
+ extra: {
65
+ accountBoundSkipped: true,
66
+ envDeclared: Array.from(DECLARED_ENV),
67
+ throwawayPrefixes: { namespaceTitle: NAMESPACE_PREFIX, kvKey: KEY_PREFIX },
68
+ },
69
+ };
70
+ }
26
71
 
27
72
  export default async function runProbe() {
28
73
  if (process.env.CI_HAS_CLOUDFLARE_ACCOUNT !== 'true') {
74
+ return skipResult('accountBoundSkipped: CI_HAS_CLOUDFLARE_ACCOUNT is not set to true; spec section 3.5 pass-with-skip. Declared env for a live run: CI_HAS_CLOUDFLARE_ACCOUNT + CF_ACCOUNT_ID + CF_API_TOKEN (CF_API_BASE_URL optional test override).');
75
+ }
76
+ if (!process.env.CF_ACCOUNT_ID || !process.env.CF_API_TOKEN) {
29
77
  return {
30
78
  results: [{
31
- anchorAcId: 'AC-5401-1',
32
- verdict: 'pass',
33
- detail: 'accountBoundSkipped: CI_HAS_CLOUDFLARE_ACCOUNT is not set to true; spec section 3.5 pass-with-skip.',
79
+ anchorAcId: 'AC-31103-1',
80
+ verdict: 'fail',
81
+ detail: `CI_HAS_CLOUDFLARE_ACCOUNT=true but one of CF_ACCOUNT_ID / CF_API_TOKEN is missing: accountIdPresent=${!!process.env.CF_ACCOUNT_ID} tokenPresent=${!!process.env.CF_API_TOKEN}.`,
34
82
  }],
35
- extra: { accountBoundSkipped: true },
83
+ extra: {
84
+ envDeclared: Array.from(DECLARED_ENV),
85
+ throwawayPrefixes: { namespaceTitle: NAMESPACE_PREFIX, kvKey: KEY_PREFIX },
86
+ },
36
87
  };
37
88
  }
38
89
 
39
- const accountId = process.env.CF_ACCOUNT_ID;
40
- const namespaceId = process.env.CF_KV_NAMESPACE_ID;
41
- const token = process.env.CF_API_TOKEN;
42
- if (!accountId || !namespaceId || !token) {
90
+ const runId = process.env.GITHUB_RUN_ID || `local-${Date.now()}`;
91
+ let namespace = null;
92
+ const evidence = {
93
+ envDeclared: Array.from(DECLARED_ENV),
94
+ throwawayPrefixes: { namespaceTitle: NAMESPACE_PREFIX, kvKey: KEY_PREFIX },
95
+ runId,
96
+ };
97
+ try {
98
+ namespace = await mintScratchNamespace({ runId });
99
+ evidence.namespaceId = namespace.id;
100
+ evidence.namespaceTitle = namespace.title;
101
+ } catch (err) {
43
102
  return {
44
103
  results: [{
45
- anchorAcId: 'AC-5401-1',
104
+ anchorAcId: 'AC-31103-1',
46
105
  verdict: 'fail',
47
- detail: `CI_HAS_CLOUDFLARE_ACCOUNT=true but one of CF_ACCOUNT_ID/CF_KV_NAMESPACE_ID/CF_API_TOKEN is missing: accountIdPresent=${!!accountId} namespaceIdPresent=${!!namespaceId} tokenPresent=${!!token}`,
106
+ detail: `mintScratchNamespace failed: ${err.message}`,
48
107
  }],
108
+ extra: evidence,
49
109
  };
50
110
  }
51
111
 
52
- const key = `platform-cloudflare-kv/real-account-smoke/${Date.now()}`;
112
+ const key = generateKey({ runId });
53
113
  const value = `smoke-${Math.random().toString(36).slice(2)}`;
54
- const putUrl = `${API_BASE}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`;
55
- const authHeaders = { authorization: `Bearer ${token}`, 'content-type': 'text/plain' };
114
+ evidence.key = key;
115
+ evidence.valueSample = value;
56
116
 
57
- const putResp = await fetch(putUrl, { method: 'PUT', headers: authHeaders, body: value });
58
- if (!putResp.ok) {
59
- return {
60
- results: [{
61
- anchorAcId: 'AC-5401-1',
62
- verdict: 'fail',
63
- detail: `real-account smoke: PUT returned status ${putResp.status}`,
64
- }],
65
- };
66
- }
117
+ try {
118
+ const put = await putScratchKey({ namespaceId: namespace.id, key, value });
119
+ evidence.putStatus = put.status;
67
120
 
68
- const start = Date.now();
69
- let observed = null;
70
- while (Date.now() - start < CAP_MS) {
71
- const getResp = await fetch(putUrl, { method: 'GET', headers: { authorization: `Bearer ${token}` } });
72
- if (getResp.ok) {
73
- const body = await getResp.text();
74
- if (body === value) {
75
- observed = body;
121
+ const start = Date.now();
122
+ let observed = null;
123
+ while (Date.now() - start < CAP_MS) {
124
+ const got = await getScratchKey({ namespaceId: namespace.id, key });
125
+ if (got.ok && got.text === value) {
126
+ observed = got.text;
127
+ evidence.getStatusOnHit = got.status;
76
128
  break;
77
129
  }
130
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
78
131
  }
79
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
80
- }
132
+ const elapsed = Date.now() - start;
133
+ evidence.elapsedMs = elapsed;
134
+ evidence.capMs = CAP_MS;
81
135
 
82
- const elapsed = Date.now() - start;
136
+ // Delete the scratch key regardless of outcome; the namespace
137
+ // destroy in the finally block would remove it anyway, but the
138
+ // per-key delete proves the delete verb on the shipped path.
139
+ try {
140
+ const del = await deleteScratchKey({ namespaceId: namespace.id, key });
141
+ evidence.deleteStatus = del.status;
142
+ } catch (err) {
143
+ evidence.deleteError = err.message;
144
+ }
83
145
 
84
- // Clean up the written key regardless of outcome.
85
- try {
86
- await fetch(putUrl, { method: 'DELETE', headers: { authorization: `Bearer ${token}` } });
87
- } catch (_err) {
88
- // Best effort; do not fail the probe on cleanup.
146
+ const pass = observed === value;
147
+ return {
148
+ results: [{
149
+ anchorAcId: 'AC-31103-1',
150
+ verdict: pass ? 'pass' : 'fail',
151
+ detail: pass
152
+ ? `real-account KV eventual-consistency: mint namespace ${namespace.id} (${namespace.title}); PUT key ${key} (status ${evidence.putStatus}); write eventually appeared on same-region GET within elapsed=${elapsed}ms (CAP=${CAP_MS}ms); DELETE key (status ${evidence.deleteStatus ?? 'unset'}); namespace destroyed on teardown.`
153
+ : `real-account KV eventual-consistency: mint namespace ${namespace.id} (${namespace.title}); PUT key ${key} (status ${evidence.putStatus}); write did NOT appear within CAP=${CAP_MS}ms; elapsed=${elapsed}ms.`,
154
+ }],
155
+ extra: evidence,
156
+ };
157
+ } finally {
158
+ try {
159
+ const teardown = await destroyScratchNamespace(namespace);
160
+ evidence.namespaceDestroyed = teardown.destroyed;
161
+ } catch (err) {
162
+ evidence.teardownError = err.message;
163
+ process.stderr.write(`h2-cf-kv probe: teardown failed for namespace ${namespace && namespace.id}: ${err.message}; sweepOrphans will collect on next run.\n`);
164
+ }
89
165
  }
90
-
91
- const pass = observed === value;
92
- return {
93
- results: [{
94
- anchorAcId: 'AC-5401-1',
95
- verdict: pass ? 'pass' : 'fail',
96
- detail: pass
97
- ? `write eventually appeared on the same-region read within elapsed=${elapsed}ms (CAP=${CAP_MS}ms)`
98
- : `write did not appear within CAP=${CAP_MS}ms; elapsed=${elapsed}ms`,
99
- }],
100
- extra: { elapsedMs: elapsed, cap: CAP_MS },
101
- };
102
166
  }
@@ -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 31101-31108 sit at the LOW end of the 31101-31899 band on purpose (watchpost run-4 lesson). A project-side story mechanically derived from a platform-cloudflare-kv REQ id into the number 31110 would collide against a shipped US-31110; band headroom (31109-31899) leaves that space.
60
63
 
@@ -0,0 +1,12 @@
1
+ # platform-docker-compose-host CHANGELOG
2
+
3
+ ## 1.0.0 - 2026-09-08
4
+
5
+ Initial release. Round-7 T-2 of the Hetzner spec at `projects/blueprint-library/specs/hetzner-round-7-spec-2026-09-07.md`.
6
+
7
+ - Mints capability `containerHost` and global topic `containerHostContract`.
8
+ - Six REQs, nine USs, four TACs, four ADRs; five Node-only probes.
9
+ - Extends the shared throwaway-Hetzner-server fixture with a minimal compose stack (`compose.yaml`, `caddy/Caddyfile`, `secrets/web-token` file mount, `src/serve.mjs` healthchecked stub) plus five `run-<probe>.mjs` delegate shims.
10
+ - Compose-config-lint carries four mutation switches (`SIMULATE_MISSING_HEALTHCHECK`, `SIMULATE_UNCLASSIFIED_RESTART`, `SIMULATE_UNCLASSIFIED_LOG_DRIVER`, `SIMULATE_EVENT_SECRECY_LEAK`); secrets-as-files-scan carries `SIMULATE_PLAINTEXT_SECRET`; caddyfile-validate carries `SIMULATE_INVALID_CADDYFILE` and runs `caddy validate` via the `caddy:2` container when a local `caddy` binary is not on PATH.
11
+ - Reverse-proxy choice elicited via `reverse-proxy` (caddy default per Baz decision 19; traefik and none alternatives); Coolify rejected verbatim per Baz decision 20; log driver elicited via `log-driver` (journald default; loki opt-in).
12
+ - Consumers: `w-2026-09-07-dave-004` migrates the ops-01 remote-library compose stack onto this contract after 0.25.0 ships; blueprint acceptance never depends on that migration.