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,168 @@
1
+ {
2
+ "slug": "edge-cloudflare-tunnel",
3
+ "version": "1.0.1",
4
+ "category": "edge",
5
+ "capabilities": [
6
+ "tunnelBridge"
7
+ ],
8
+ "suggestedCompanions": [
9
+ {
10
+ "role": "logging",
11
+ "reason": "Every cloudflaredReady, tunnelConnectorUp, tunnelConnectorDown, ingressRuleReloaded and accessGateRefused event writes through the applied logger; a logging companion supplies the factory."
12
+ },
13
+ {
14
+ "role": "errorHandling",
15
+ "reason": "A cloudflared exit, a JWT-check failure, an ingress-rule schema violation, a hostname-DNS-route API refusal constructs an internal error record; an error-handling companion supplies the record factory and the boundary."
16
+ }
17
+ ],
18
+ "elicits": [
19
+ {
20
+ "id": "tunnel-name",
21
+ "prompt": "The Cloudflare Tunnel name (also the manifest filename under cloudflare/tunnels/<name>.yaml). Names the connector in cloudflared tunnel list and in the routing DNS entries.",
22
+ "kind": "string",
23
+ "default": "probe"
24
+ },
25
+ {
26
+ "id": "cloudflare-zone-secret",
27
+ "prompt": "security-secrets-management reference to the Cloudflare zone the tunnel serves under. Read at apply time to create the DNS route; never inlined into the manifest.",
28
+ "kind": "string",
29
+ "default": "hq-estate/CLOUDFLARE_ZONE_ID"
30
+ },
31
+ {
32
+ "id": "public-hostname-template",
33
+ "prompt": "Template for the public hostname (e.g. <service>.<zone>). The template is expanded per ingress rule at apply time.",
34
+ "kind": "string",
35
+ "default": "<service>.<zone>"
36
+ },
37
+ {
38
+ "id": "ingress-rules",
39
+ "prompt": "Ingress rules array: each entry names a public hostname and the internal service URL to route to. The last entry is a mandatory catch-all http_status 404 (added by the blueprint if omitted).",
40
+ "kind": "string",
41
+ "default": "[]"
42
+ },
43
+ {
44
+ "id": "access-aud",
45
+ "prompt": "Optional Access AUD tag for the Access-gated shape. When present AND edge-cloudflare-access is applied (zeroTrustGate on appliedCapabilities), every ingress rule attaches originRequest.access.aud with this value. When absent OR edge-cloudflare-access is not applied, the block is omitted and the tunnel serves in public-hostname mode.",
46
+ "kind": "string",
47
+ "default": ""
48
+ },
49
+ {
50
+ "id": "connector-runtime",
51
+ "prompt": "Connector runtime per ADR-4002. compose-service is the shipped default when containerHost is applied (T-2 platform-docker-compose-host on top of T-1 deploy-hetzner-server); systemd-unit is the alternative for a bare cloudHost per the cloudflared as-a-service local-configuration-file docs.",
52
+ "kind": "enum",
53
+ "options": [
54
+ "compose-service",
55
+ "systemd-unit"
56
+ ],
57
+ "default": "compose-service"
58
+ }
59
+ ],
60
+ "contributions": [
61
+ {
62
+ "id": "edge-cloudflare-tunnel-REQ-001",
63
+ "kind": "req",
64
+ "path": "requirements/edge-cloudflare-tunnel-req-001.json"
65
+ },
66
+ {
67
+ "id": "edge-cloudflare-tunnel-REQ-002",
68
+ "kind": "req",
69
+ "path": "requirements/edge-cloudflare-tunnel-req-002.json"
70
+ },
71
+ {
72
+ "id": "edge-cloudflare-tunnel-REQ-003",
73
+ "kind": "req",
74
+ "path": "requirements/edge-cloudflare-tunnel-req-003.json"
75
+ },
76
+ {
77
+ "id": "edge-cloudflare-tunnel-REQ-004",
78
+ "kind": "req",
79
+ "path": "requirements/edge-cloudflare-tunnel-req-004.json"
80
+ },
81
+ {
82
+ "id": "edge-cloudflare-tunnel-REQ-005",
83
+ "kind": "req",
84
+ "path": "requirements/edge-cloudflare-tunnel-req-005.json"
85
+ },
86
+ {
87
+ "id": "edge-cloudflare-tunnel-US-39101",
88
+ "kind": "us",
89
+ "path": "user-stories/edge-cloudflare-tunnel-us-39101.json"
90
+ },
91
+ {
92
+ "id": "edge-cloudflare-tunnel-US-39102",
93
+ "kind": "us",
94
+ "path": "user-stories/edge-cloudflare-tunnel-us-39102.json"
95
+ },
96
+ {
97
+ "id": "edge-cloudflare-tunnel-US-39103",
98
+ "kind": "us",
99
+ "path": "user-stories/edge-cloudflare-tunnel-us-39103.json"
100
+ },
101
+ {
102
+ "id": "edge-cloudflare-tunnel-US-39104",
103
+ "kind": "us",
104
+ "path": "user-stories/edge-cloudflare-tunnel-us-39104.json"
105
+ },
106
+ {
107
+ "id": "edge-cloudflare-tunnel-US-39105",
108
+ "kind": "us",
109
+ "path": "user-stories/edge-cloudflare-tunnel-us-39105.json"
110
+ },
111
+ {
112
+ "id": "edge-cloudflare-tunnel-US-39106",
113
+ "kind": "us",
114
+ "path": "user-stories/edge-cloudflare-tunnel-us-39106.json"
115
+ },
116
+ {
117
+ "id": "edge-cloudflare-tunnel-US-39107",
118
+ "kind": "us",
119
+ "path": "user-stories/edge-cloudflare-tunnel-us-39107.json"
120
+ },
121
+ {
122
+ "id": "edge-cloudflare-tunnel-US-39108",
123
+ "kind": "us",
124
+ "path": "user-stories/edge-cloudflare-tunnel-us-39108.json"
125
+ },
126
+ {
127
+ "id": "TAC-4001-edge-cloudflare-tunnel-connector-shape",
128
+ "kind": "tac",
129
+ "path": "tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json"
130
+ },
131
+ {
132
+ "id": "TAC-4002-edge-cloudflare-tunnel-manifest-schema",
133
+ "kind": "tac",
134
+ "path": "tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json"
135
+ },
136
+ {
137
+ "id": "TAC-4003-edge-cloudflare-tunnel-access-binding",
138
+ "kind": "tac",
139
+ "path": "tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json"
140
+ },
141
+ {
142
+ "id": "ADR-4001-edge-cloudflare-tunnel-ingress-bridge-contract",
143
+ "kind": "adr",
144
+ "path": "adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json",
145
+ "scope": "global",
146
+ "topic": "edgeIngressBridge",
147
+ "recommendedDefault": true,
148
+ "elicited": false,
149
+ "standardsTraceClause": "Cloudflare Tunnel overview and get-started documented mechanics per hetzner-round-7-spec-2026-09-07.md"
150
+ },
151
+ {
152
+ "id": "ADR-4002-edge-cloudflare-tunnel-connector-runtime",
153
+ "kind": "adr",
154
+ "path": "adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json",
155
+ "recommendedDefault": true,
156
+ "elicited": true,
157
+ "standardsTraceClause": "Cloudflare cloudflared as-a-service documented systemd unit per hetzner-round-7-spec-2026-09-07.md"
158
+ },
159
+ {
160
+ "id": "ADR-4003-edge-cloudflare-tunnel-hostname-mode",
161
+ "kind": "adr",
162
+ "path": "adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json",
163
+ "recommendedDefault": true,
164
+ "elicited": false,
165
+ "standardsTraceClause": "Cloudflare Access self-hosted-public-app documented shape per hetzner-round-7-spec-2026-09-07.md"
166
+ }
167
+ ]
168
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "adrId": "ADR-4001-edge-cloudflare-tunnel-ingress-bridge-contract",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Edge ingress bridge contract: Cloudflare Tunnel as the shipped ingress-bridge shape from a cloudHost service to the CF edge (scope global, new topic edgeIngressBridge)",
8
+ "context": "An applying project running on a Linux cloudHost needs one shipped answer to the question of how a service on that host reaches the public internet without opening origin ports. Round-7 T-3 mints the edgeIngressBridge topic on this ADR. Cloudflare Tunnel is the shipped v1.0.0 mechanism (cloudflared connector out to the CF edge; DNS route on a Cloudflare zone; ingress rules mapping public hostname to internal service URL). Every applied ingress bridge needs one project-wide answer to the edgeIngressBridge topic; a future non-Cloudflare sibling (Tailscale Funnel, Fly.io proxy) would contribute the same topic string with a different mechanism, forcing a DELIBERATE conflict the operator resolves per the round-2 conflict pattern. Standards trace clause: Cloudflare Tunnel overview and get-started documented mechanics per hetzner-round-7-spec-2026-09-07.md.",
9
+ "decision": "The shipped v1.0.0 default is a Cloudflare Tunnel connector as the edgeIngressBridge provider. A cloudflared connector on the applying host (compose service by default when containerHost is applied per ADR-4002; systemd unit on a bare cloudHost) reads a tunnel manifest at cloudflare/tunnels/<name>.yaml and terminates ingress at the CF edge. The edgeIngressBridge topic scope is global on this blueprint; the topic string is edgeIngressBridge (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: Cloudflare Tunnel overview and get-started documented mechanics per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "Consumer blueprints that expose a service publicly declare capabilities: [tunnelBridge] on their applied contract to know a bridge is in place. Composition with zeroTrustGate (edge-cloudflare-access) toggles the Access-gated ingress shape per ADR-4003. A T-3-only project (no reverse proxy) declares no 80/443 firewall rule; the tunnel is the sole path from the edge to the origin. Rebuild-from-manifest is always possible: the manifest is the source of truth, the running connector is derivative. Folding this shape into edge-cloudflare-access was rejected: Access is an authorisation gate, Tunnel is an ingress bridge; the responsibilities are distinct and one applies without the other.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Fold into edge-cloudflare-access as an adapter",
14
+ "reason": "Access is an authorisation gate on top of an ingress; Tunnel is an ingress bridge. One applies without the other. Folding conflated two decisions the operator ships separately. Rejected per Baz ruling decision 21."
15
+ },
16
+ {
17
+ "name": "Ship a Tailscale Funnel or Fly.io proxy sibling in the same PR",
18
+ "reason": "Out of scope for round 7. Siblings mint on operator demand into their own reserved slots and conflict-by-design with this blueprint on the edgeIngressBridge topic."
19
+ }
20
+ ],
21
+ "createdAt": "2026-09-08T00:00:00.000Z",
22
+ "updatedAt": "2026-09-08T00:00:00.000Z"
23
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "adrId": "ADR-4002-edge-cloudflare-tunnel-connector-runtime",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Connector runtime: compose-service default when containerHost applied; systemd-unit alternative on bare cloudHost",
8
+ "context": "The cloudflared connector runs in one of two supported vendor shapes: as a container image published on the cloudflared downloads page, or as a systemd unit installed via the vendor package per the cloudflared as-a-service local-configuration-file docs. An applying project running its stack under docker compose has a natural home for the connector alongside its other services (same network, same logging driver, same restart policy). An applying project running bare on the host has no compose stack and needs a systemd unit for boot ordering and journald logging. Standards trace clause: Cloudflare cloudflared as-a-service documented systemd unit per hetzner-round-7-spec-2026-09-07.md.",
9
+ "decision": "The connector runtime is elicited via connector-runtime with enum {compose-service, systemd-unit} and recommendedDefault compose-service. When the containerHost capability is applied (T-2 platform-docker-compose-host) the shipped default is compose-service; when the host runs bare (only T-1 applied) the operator picks systemd-unit at apply time. Standards trace clause: Cloudflare cloudflared as-a-service documented systemd unit per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "The fixture ships both variants and both pass the local probes (manifest-schema-validate, cloudflared-config-lint, aud-presence-check). A future non-Cloudflare tunnel sibling (Tailscale Funnel, Fly.io proxy) reuses the runtime enum shape but the elicit lives on that sibling own ADR (conflict-by-design on edgeIngressBridge per ADR-4001). The systemd variant carries an install caveat in the guide: the vendor package landed via apt or curl per the as-a-service docs, which is out of scope for the blueprint itself.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Ship only the compose-service variant",
14
+ "reason": "A bare cloudHost is a valid apply shape; forcing docker compose onto a host that does not want it is a hard cost the elicit avoids."
15
+ }
16
+ ],
17
+ "createdAt": "2026-09-08T00:00:00.000Z",
18
+ "updatedAt": "2026-09-08T00:00:00.000Z"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "adrId": "ADR-4003-edge-cloudflare-tunnel-hostname-mode",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Hostname mode: access-gated when zeroTrustGate applied (ingress attaches AUD); public-hostname when absent (block omitted)",
8
+ "context": "A tunnel can serve a public hostname openly (any request reaches the origin, protected only by whatever authentication the origin itself carries) or Access-gated (cloudflared refuses at the edge any request without a valid Access JWT for a named AUD, per the vendor self-hosted-public-app docs). The hostname mode is not elicited: it is DISCOVERED from the applied capability set. When zeroTrustGate is applied (edge-cloudflare-access v1.0.0), the tunnel binds the Access AUD; when it is not, the tunnel serves in public-hostname mode. Standards trace clause: Cloudflare Access self-hosted-public-app documented shape per hetzner-round-7-spec-2026-09-07.md.",
9
+ "decision": "ADR-4003 records the hostname mode as elicited: false and discovered from applied capabilities via the sidecar rcf/blueprints/edge-cloudflare-tunnel.applied.json. When appliedCapabilities includes zeroTrustGate, every ingress rule under the tunnel manifest attaches an originRequest.access block naming the AUD; when it does not, the block is omitted. The aud-presence-check probe runs on both fixture variants and asserts the shape flips correctly. Standards trace clause: Cloudflare Access self-hosted-public-app documented shape per hetzner-round-7-spec-2026-09-07.md.",
10
+ "consequences": "A project that applies zeroTrustGate later inherits the Access-gated shape on the next reapply of edge-cloudflare-tunnel without a manual manifest edit; a project that removes zeroTrustGate drops to public-hostname mode explicitly. A silent drift (the sidecar keeps zeroTrustGate but the ingress loses the AUD block) is caught by aud-presence-check refusing before the shape ships. Fixture variants live under cloudflared/sidecars/{access-gated,public-hostname}.applied.json; the SIMULATE_AUD_DROP mutation on the gated fixture proves the refuse path.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Elicit the hostname mode as a separate parameter",
14
+ "reason": "The applied capability set is already the source of truth; a separate elicit would drift from that set. Discovery keeps the shape aligned by construction."
15
+ }
16
+ ],
17
+ "createdAt": "2026-09-08T00:00:00.000Z",
18
+ "updatedAt": "2026-09-08T00:00:00.000Z"
19
+ }
@@ -0,0 +1,114 @@
1
+ // Probe: aud-presence-check.
2
+ //
3
+ // anchorAcId list:
4
+ // - AC-tunnel-accessGated (primary)
5
+ // - AC-tunnel-accessGatedRefuse (drift refuse)
6
+ // accountBound: false.
7
+ //
8
+ // The probe reads the two sidecars under fixtureRoot()/sidecars and the
9
+ // two manifests per runtime under fixtureRoot()/<runtime>/cloudflare/tunnels/<mode>.yaml.
10
+ // A fixture-side delegate points FIXTURE_ROOT at a scratch copy to drive
11
+ // the fixture-side aud-drop mutation. The probe module itself is a pure
12
+ // function of the files it sees.
13
+ //
14
+ // Passes when:
15
+ // - Each access-gated manifest carries originRequest.access.aud equal to
16
+ // the gated sidecar accessAud on every non-catch-all ingress rule.
17
+ // - Each public-hostname manifest carries no originRequest.access block.
18
+ //
19
+ // Fails when the gated variant drifts to omit the AUD block (the drift-
20
+ // refuse case). Also fails when the public-hostname variant carries an
21
+ // AUD block (that shape has no source of authority).
22
+
23
+ import { readFile } from 'node:fs/promises';
24
+ import { resolve } from 'node:path';
25
+ import { runShim, fixtureRoot, parseTunnelYaml, RUNTIMES } from './probe-utils.mjs';
26
+
27
+ export const anchorAcIds = ['AC-tunnel-accessGated', 'AC-tunnel-accessGatedRefuse'];
28
+ export const accountBound = false;
29
+
30
+ async function loadSidecar(root, name) {
31
+ const path = resolve(root, 'sidecars', `${name}.applied.json`);
32
+ const text = await readFile(path, 'utf8');
33
+ return JSON.parse(text);
34
+ }
35
+
36
+ async function loadManifest(root, runtime, mode) {
37
+ const path = resolve(root, runtime, 'cloudflare/tunnels', `${mode}.yaml`);
38
+ const text = await readFile(path, 'utf8');
39
+ return parseTunnelYaml(text);
40
+ }
41
+
42
+ export default async function runProbe() {
43
+ const results = [];
44
+ const extra = { fixtureRoot: fixtureRoot() };
45
+ const root = fixtureRoot();
46
+ const gatedSidecar = await loadSidecar(root, 'access-gated');
47
+ const publicSidecar = await loadSidecar(root, 'public-hostname');
48
+ const gatedHasZtg = (gatedSidecar.appliedCapabilities ?? []).includes('zeroTrustGate');
49
+ const publicHasZtg = (publicSidecar.appliedCapabilities ?? []).includes('zeroTrustGate');
50
+ extra.gatedSidecarZtg = gatedHasZtg;
51
+ extra.publicSidecarZtg = publicHasZtg;
52
+ if (!gatedHasZtg) {
53
+ results.push({
54
+ anchorAcId: 'AC-tunnel-accessGated',
55
+ verdict: 'fail',
56
+ detail: `access-gated sidecar is missing zeroTrustGate in appliedCapabilities (found: ${(gatedSidecar.appliedCapabilities ?? []).join(', ')})`,
57
+ });
58
+ }
59
+ if (publicHasZtg) {
60
+ results.push({
61
+ anchorAcId: 'AC-tunnel-accessGated',
62
+ verdict: 'fail',
63
+ detail: `public-hostname sidecar unexpectedly names zeroTrustGate in appliedCapabilities (found: ${(publicSidecar.appliedCapabilities ?? []).join(', ')})`,
64
+ });
65
+ }
66
+ for (const runtime of RUNTIMES) {
67
+ // access-gated manifest: every non-catch-all rule attaches the AUD.
68
+ const gDoc = await loadManifest(root, runtime, 'access-gated');
69
+ const gRules = Array.isArray(gDoc.ingress) ? gDoc.ingress : [];
70
+ const gNonCatch = gRules.filter((r) => !String(r.service ?? '').startsWith('http_status:'));
71
+ const gAttached = gNonCatch.filter((r) => r?.originRequest?.access?.aud === gatedSidecar.accessAud);
72
+ if (gAttached.length === gNonCatch.length && gNonCatch.length > 0) {
73
+ results.push({
74
+ anchorAcId: 'AC-tunnel-accessGated',
75
+ verdict: 'pass',
76
+ detail: `${runtime}/access-gated: all ${gNonCatch.length} non-catch-all rule(s) attach originRequest.access.aud=${gatedSidecar.accessAud}`,
77
+ });
78
+ } else {
79
+ const missing = gNonCatch
80
+ .filter((r) => r?.originRequest?.access?.aud !== gatedSidecar.accessAud)
81
+ .map((r, i) => `service=${r.service} aud=${r?.originRequest?.access?.aud ?? '(none)'}`);
82
+ results.push({
83
+ anchorAcId: 'AC-tunnel-accessGatedRefuse',
84
+ verdict: 'fail',
85
+ detail: `${runtime}/access-gated: sidecar declares zeroTrustGate + accessAud=${gatedSidecar.accessAud} but ${gNonCatch.length - gAttached.length}/${gNonCatch.length} rule(s) drift (missing or wrong AUD). Drifted: ${missing.slice(0, 3).join(' | ')}`,
86
+ });
87
+ }
88
+ // public-hostname manifest: no rule carries originRequest.access.
89
+ const pDoc = await loadManifest(root, runtime, 'public-hostname');
90
+ const pRules = Array.isArray(pDoc.ingress) ? pDoc.ingress : [];
91
+ const carriers = pRules
92
+ .map((r, i) => ({ i, r }))
93
+ .filter(({ r }) => r?.originRequest?.access);
94
+ if (carriers.length === 0) {
95
+ results.push({
96
+ anchorAcId: 'AC-tunnel-accessGated',
97
+ verdict: 'pass',
98
+ detail: `${runtime}/public-hostname: manifest omits originRequest.access on every ingress rule (open ingress by design)`,
99
+ });
100
+ } else {
101
+ results.push({
102
+ anchorAcId: 'AC-tunnel-accessGated',
103
+ verdict: 'fail',
104
+ detail: `${runtime}/public-hostname: sidecar declares no zeroTrustGate but ${carriers.length} ingress rule(s) carry originRequest.access; that shape has no source of authority`,
105
+ });
106
+ }
107
+ }
108
+ return { results, extra };
109
+ }
110
+
111
+ const engine = { kind: 'sidecar-read', image: 'edge-cloudflare-tunnel aud-presence facade', healthy: true };
112
+ if (import.meta.url === `file://${process.argv[1]}`) {
113
+ await runShim('aud-presence-check', engine, runProbe);
114
+ }
@@ -0,0 +1,80 @@
1
+ // Probe: cloudflared-config-lint.
2
+ //
3
+ // anchorAcId list: AC-tunnel-hostnameRoutes (primary).
4
+ // accountBound: false.
5
+ //
6
+ // Verdict is a pure function of the files at fixtureRoot(). The probe:
7
+ // 1. Locates cloudflared (local binary on PATH first, else the vendor
8
+ // container image cloudflare/cloudflared:2026.8.3 via docker run --rm).
9
+ // 2. Runs `cloudflared tunnel ingress validate --config /etc/cloudflared/<mode>.yaml`
10
+ // against each fixture (compose-service and systemd-unit, both public-
11
+ // hostname and access-gated modes); asserts exit 0 on the canonical fixture.
12
+ // 3. When neither cloudflared nor docker is available, records warn (the
13
+ // schema-validate probe still surfaces the source-tree shape).
14
+
15
+ import { spawnSync } from 'node:child_process';
16
+ import { resolve } from 'node:path';
17
+ import {
18
+ runShim, fixtureRoot, pairs, whichDocker, whichCloudflared, CLOUDFLARED_IMAGE,
19
+ } from './probe-utils.mjs';
20
+
21
+ export const anchorAcIds = ['AC-tunnel-hostnameRoutes'];
22
+ export const accountBound = false;
23
+
24
+ function runCloudflaredLint({ manifestDir, manifestName, useDocker, cloudflaredVersion }) {
25
+ if (!useDocker) {
26
+ const r = spawnSync('cloudflared', ['tunnel', '--config', resolve(manifestDir, manifestName), 'ingress', 'validate'], {
27
+ encoding: 'utf8', timeout: 60_000,
28
+ });
29
+ return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '', engine: `cloudflared ${cloudflaredVersion}` };
30
+ }
31
+ const r = spawnSync('docker', [
32
+ 'run', '--rm', '-v', `${manifestDir}:/etc/cloudflared:ro`,
33
+ CLOUDFLARED_IMAGE, 'tunnel', '--config', `/etc/cloudflared/${manifestName}`, 'ingress', 'validate',
34
+ ], { encoding: 'utf8', timeout: 120_000 });
35
+ return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '', engine: `docker ${CLOUDFLARED_IMAGE}` };
36
+ }
37
+
38
+ export default async function runProbe() {
39
+ const results = [];
40
+ const extra = { fixtureRoot: fixtureRoot() };
41
+ const cloudflared = whichCloudflared();
42
+ const docker = whichDocker();
43
+ extra.cloudflaredVersion = cloudflared;
44
+ extra.dockerVersion = docker;
45
+ if (!cloudflared && !docker) {
46
+ results.push({
47
+ anchorAcId: 'AC-tunnel-hostnameRoutes',
48
+ verdict: 'warn',
49
+ detail: 'Neither a local cloudflared binary nor docker is available on PATH; cloudflared tunnel ingress validate was not exercised. Install cloudflared or docker to exercise the lint.',
50
+ });
51
+ return { results, extra };
52
+ }
53
+ const useDocker = !cloudflared;
54
+ for (const { runtime, mode } of pairs()) {
55
+ const manifestDir = resolve(fixtureRoot(), runtime, 'cloudflare/tunnels');
56
+ const manifestName = `${mode}.yaml`;
57
+ const r = runCloudflaredLint({ manifestDir, manifestName, useDocker, cloudflaredVersion: cloudflared });
58
+ if (r.status === 0) {
59
+ results.push({
60
+ anchorAcId: 'AC-tunnel-hostnameRoutes',
61
+ verdict: 'pass',
62
+ detail: `${runtime}/${mode}: cloudflared tunnel ingress validate exit 0 (${r.engine})`,
63
+ });
64
+ } else {
65
+ const tail = (r.stderr || r.stdout || '').split('\n').slice(-8).join(' | ').slice(0, 800);
66
+ results.push({
67
+ anchorAcId: 'AC-tunnel-hostnameRoutes',
68
+ verdict: 'fail',
69
+ detail: `${runtime}/${mode}: cloudflared tunnel ingress validate exit ${r.status} (${r.engine}): ${tail}`,
70
+ });
71
+ }
72
+ extra[`${runtime}/${mode}`] = { exit: r.status };
73
+ }
74
+ return { results, extra };
75
+ }
76
+
77
+ const engine = { kind: 'cloudflared-ingress-validate', image: CLOUDFLARED_IMAGE, healthy: true };
78
+ if (import.meta.url === `file://${process.argv[1]}`) {
79
+ await runShim('cloudflared-config-lint', engine, runProbe);
80
+ }