@voxgig/sdkgen 4.10.0 → 4.11.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 (304) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/package.json +1 -1
  3. package/project/.sdk/model/feature/secrets.aon +330 -0
  4. package/project/.sdk/model/target/c.aon +15 -2
  5. package/project/.sdk/model/target/cpp.aon +18 -0
  6. package/project/.sdk/model/target/lua.aon +17 -0
  7. package/project/.sdk/model/target/ocaml.aon +48 -5
  8. package/project/.sdk/model/target/zig.aon +30 -4
  9. package/project/.sdk/src/cmp/c/Config_c.ts +338 -1
  10. package/project/.sdk/src/cmp/c/Main_c.ts +57 -2
  11. package/project/.sdk/src/cmp/c/Package_c.ts +12 -2
  12. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +324 -2
  13. package/project/.sdk/src/cmp/cpp/Gitignore_cpp.ts +2 -0
  14. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +62 -4
  15. package/project/.sdk/src/cmp/cpp/Package_cpp.ts +10 -0
  16. package/project/.sdk/src/cmp/lua/Config_lua.ts +88 -0
  17. package/project/.sdk/src/cmp/lua/Gitignore_lua.ts +4 -0
  18. package/project/.sdk/src/cmp/lua/Main_lua.ts +93 -3
  19. package/project/.sdk/src/cmp/lua/Package_lua.ts +60 -0
  20. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +10 -0
  21. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +183 -5
  22. package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +160 -3
  23. package/project/.sdk/src/cmp/ocaml/Package_ocaml.ts +25 -1
  24. package/project/.sdk/src/cmp/zig/Config_zig.ts +130 -9
  25. package/project/.sdk/src/cmp/zig/Main_zig.ts +208 -4
  26. package/project/.sdk/{tm/zig/build.zig → src/cmp/zig/fragment/Build.fragment.zig} +7 -0
  27. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +25 -0
  28. package/project/.sdk/{tm/zig/root.zig → src/cmp/zig/fragment/Root.fragment.zig} +9 -0
  29. package/project/.sdk/tm/c/Makefile +58 -9
  30. package/project/.sdk/tm/c/core/sdk.h +10 -0
  31. package/project/.sdk/tm/c/feature/secrets/plugin/capability.c +133 -0
  32. package/project/.sdk/tm/c/feature/secrets/plugin/capability.h +39 -0
  33. package/project/.sdk/tm/c/feature/secrets/plugin/catalog.c +83 -0
  34. package/project/.sdk/tm/c/feature/secrets/plugin/catalog.h +48 -0
  35. package/project/.sdk/tm/c/feature/secrets/plugin/config.c +413 -0
  36. package/project/.sdk/tm/c/feature/secrets/plugin/config.h +24 -0
  37. package/project/.sdk/tm/c/feature/secrets/plugin/depend.c +264 -0
  38. package/project/.sdk/tm/c/feature/secrets/plugin/depend.h +29 -0
  39. package/project/.sdk/tm/c/feature/secrets/plugin/env.c +220 -0
  40. package/project/.sdk/tm/c/feature/secrets/plugin/env.h +19 -0
  41. package/project/.sdk/tm/c/feature/secrets/plugin/export.c +97 -0
  42. package/project/.sdk/tm/c/feature/secrets/plugin/export.h +19 -0
  43. package/project/.sdk/tm/c/feature/secrets/plugin/graph.c +237 -0
  44. package/project/.sdk/tm/c/feature/secrets/plugin/graph.h +17 -0
  45. package/project/.sdk/tm/c/feature/secrets/plugin/host.c +1494 -0
  46. package/project/.sdk/tm/c/feature/secrets/plugin/host.h +135 -0
  47. package/project/.sdk/tm/c/feature/secrets/plugin/order.c +288 -0
  48. package/project/.sdk/tm/c/feature/secrets/plugin/order.h +18 -0
  49. package/project/.sdk/tm/c/feature/secrets/plugin/point.c +141 -0
  50. package/project/.sdk/tm/c/feature/secrets/plugin/point.h +80 -0
  51. package/project/.sdk/tm/c/feature/secrets/plugin/ref.c +170 -0
  52. package/project/.sdk/tm/c/feature/secrets/plugin/ref.h +52 -0
  53. package/project/.sdk/tm/c/feature/secrets/plugin/resolve.c +98 -0
  54. package/project/.sdk/tm/c/feature/secrets/plugin/resolve.h +18 -0
  55. package/project/.sdk/tm/c/feature/secrets/plugin/types.c +150 -0
  56. package/project/.sdk/tm/c/feature/secrets/plugin/types.h +104 -0
  57. package/project/.sdk/tm/c/feature/secrets/plugin/value.c +649 -0
  58. package/project/.sdk/tm/c/feature/secrets/plugin/value.h +135 -0
  59. package/project/.sdk/tm/c/feature/secrets/plugin/version.c +151 -0
  60. package/project/.sdk/tm/c/feature/secrets/plugin/version.h +34 -0
  61. package/project/.sdk/tm/c/feature/secrets/plugins/aws.c +281 -0
  62. package/project/.sdk/tm/c/feature/secrets/plugins/azuresecrets.c +207 -0
  63. package/project/.sdk/tm/c/feature/secrets/plugins/boru.c +164 -0
  64. package/project/.sdk/tm/c/feature/secrets/plugins/clock.c +75 -0
  65. package/project/.sdk/tm/c/feature/secrets/plugins/doppler.c +141 -0
  66. package/project/.sdk/tm/c/feature/secrets/plugins/encode.c +144 -0
  67. package/project/.sdk/tm/c/feature/secrets/plugins/gcpsecrets.c +177 -0
  68. package/project/.sdk/tm/c/feature/secrets/plugins/hashicorp.c +239 -0
  69. package/project/.sdk/tm/c/feature/secrets/plugins/httpjson.c +599 -0
  70. package/project/.sdk/tm/c/feature/secrets/plugins/infisical.c +165 -0
  71. package/project/.sdk/tm/c/feature/secrets/plugins/onepassword.c +194 -0
  72. package/project/.sdk/tm/c/feature/secrets/plugins/proc.c +233 -0
  73. package/project/.sdk/tm/c/feature/secrets/plugins/secretspec.c +125 -0
  74. package/project/.sdk/tm/c/feature/secrets/plugins/sekretoplugins.h +125 -0
  75. package/project/.sdk/tm/c/feature/secrets/plugins/sha256.c +270 -0
  76. package/project/.sdk/tm/c/feature/secrets/plugins/sigv4.c +382 -0
  77. package/project/.sdk/tm/c/feature/secrets/plugins/support.h +153 -0
  78. package/project/.sdk/tm/c/feature/secrets/plugins/tls.c +420 -0
  79. package/project/.sdk/tm/c/feature/secrets/sekreto/internal.h +86 -0
  80. package/project/.sdk/tm/c/feature/secrets/sekreto/json.c +721 -0
  81. package/project/.sdk/tm/c/feature/secrets/sekreto/providers.c +813 -0
  82. package/project/.sdk/tm/c/feature/secrets/sekreto/sekreto.c +944 -0
  83. package/project/.sdk/tm/c/feature/secrets/sekreto/sekreto.h +490 -0
  84. package/project/.sdk/tm/c/feature/secrets/sekreto/util.c +442 -0
  85. package/project/.sdk/tm/c/feature/secrets.c +797 -0
  86. package/project/.sdk/tm/c/feature/secrets.h +46 -0
  87. package/project/.sdk/tm/c/src/feature/secrets/.gitkeep +0 -0
  88. package/project/.sdk/tm/c/tests/feature/secrets/secrets_test.c +802 -0
  89. package/project/.sdk/tm/clojure/feature/secrets/sdk/feature/secrets.clj +38 -11
  90. package/project/.sdk/tm/clojure/test/sdk/test/feature/secrets.clj +40 -6
  91. package/project/.sdk/tm/cpp/Makefile +48 -4
  92. package/project/.sdk/tm/cpp/feature/secrets/plugin/capability.cpp +121 -0
  93. package/project/.sdk/tm/cpp/feature/secrets/plugin/capability.hpp +41 -0
  94. package/project/.sdk/tm/cpp/feature/secrets/plugin/catalog.cpp +60 -0
  95. package/project/.sdk/tm/cpp/feature/secrets/plugin/catalog.hpp +66 -0
  96. package/project/.sdk/tm/cpp/feature/secrets/plugin/config.cpp +376 -0
  97. package/project/.sdk/tm/cpp/feature/secrets/plugin/config.hpp +29 -0
  98. package/project/.sdk/tm/cpp/feature/secrets/plugin/depend.cpp +230 -0
  99. package/project/.sdk/tm/cpp/feature/secrets/plugin/depend.hpp +31 -0
  100. package/project/.sdk/tm/cpp/feature/secrets/plugin/env.cpp +205 -0
  101. package/project/.sdk/tm/cpp/feature/secrets/plugin/env.hpp +25 -0
  102. package/project/.sdk/tm/cpp/feature/secrets/plugin/export.cpp +84 -0
  103. package/project/.sdk/tm/cpp/feature/secrets/plugin/export.hpp +24 -0
  104. package/project/.sdk/tm/cpp/feature/secrets/plugin/graph.cpp +222 -0
  105. package/project/.sdk/tm/cpp/feature/secrets/plugin/graph.hpp +21 -0
  106. package/project/.sdk/tm/cpp/feature/secrets/plugin/host.cpp +1200 -0
  107. package/project/.sdk/tm/cpp/feature/secrets/plugin/host.hpp +250 -0
  108. package/project/.sdk/tm/cpp/feature/secrets/plugin/order.cpp +250 -0
  109. package/project/.sdk/tm/cpp/feature/secrets/plugin/order.hpp +22 -0
  110. package/project/.sdk/tm/cpp/feature/secrets/plugin/point.cpp +110 -0
  111. package/project/.sdk/tm/cpp/feature/secrets/plugin/point.hpp +79 -0
  112. package/project/.sdk/tm/cpp/feature/secrets/plugin/ref.cpp +136 -0
  113. package/project/.sdk/tm/cpp/feature/secrets/plugin/ref.hpp +60 -0
  114. package/project/.sdk/tm/cpp/feature/secrets/plugin/resolve.cpp +82 -0
  115. package/project/.sdk/tm/cpp/feature/secrets/plugin/resolve.hpp +25 -0
  116. package/project/.sdk/tm/cpp/feature/secrets/plugin/types.cpp +90 -0
  117. package/project/.sdk/tm/cpp/feature/secrets/plugin/types.hpp +65 -0
  118. package/project/.sdk/tm/cpp/feature/secrets/plugin/value.cpp +491 -0
  119. package/project/.sdk/tm/cpp/feature/secrets/plugin/value.hpp +120 -0
  120. package/project/.sdk/tm/cpp/feature/secrets/plugin/version.cpp +143 -0
  121. package/project/.sdk/tm/cpp/feature/secrets/plugin/version.hpp +36 -0
  122. package/project/.sdk/tm/cpp/feature/secrets/plugins/Aws.cpp +248 -0
  123. package/project/.sdk/tm/cpp/feature/secrets/plugins/Aws.hpp +30 -0
  124. package/project/.sdk/tm/cpp/feature/secrets/plugins/Azuresecrets.cpp +149 -0
  125. package/project/.sdk/tm/cpp/feature/secrets/plugins/Azuresecrets.hpp +23 -0
  126. package/project/.sdk/tm/cpp/feature/secrets/plugins/Boru.cpp +117 -0
  127. package/project/.sdk/tm/cpp/feature/secrets/plugins/Boru.hpp +27 -0
  128. package/project/.sdk/tm/cpp/feature/secrets/plugins/Crypto.cpp +180 -0
  129. package/project/.sdk/tm/cpp/feature/secrets/plugins/Crypto.hpp +52 -0
  130. package/project/.sdk/tm/cpp/feature/secrets/plugins/Doppler.cpp +87 -0
  131. package/project/.sdk/tm/cpp/feature/secrets/plugins/Doppler.hpp +23 -0
  132. package/project/.sdk/tm/cpp/feature/secrets/plugins/Gcpsecrets.cpp +121 -0
  133. package/project/.sdk/tm/cpp/feature/secrets/plugins/Gcpsecrets.hpp +23 -0
  134. package/project/.sdk/tm/cpp/feature/secrets/plugins/Hashicorp.cpp +153 -0
  135. package/project/.sdk/tm/cpp/feature/secrets/plugins/Hashicorp.hpp +23 -0
  136. package/project/.sdk/tm/cpp/feature/secrets/plugins/Httpjson.cpp +666 -0
  137. package/project/.sdk/tm/cpp/feature/secrets/plugins/Httpjson.hpp +160 -0
  138. package/project/.sdk/tm/cpp/feature/secrets/plugins/Infisical.cpp +123 -0
  139. package/project/.sdk/tm/cpp/feature/secrets/plugins/Infisical.hpp +23 -0
  140. package/project/.sdk/tm/cpp/feature/secrets/plugins/Onepassword.cpp +125 -0
  141. package/project/.sdk/tm/cpp/feature/secrets/plugins/Onepassword.hpp +23 -0
  142. package/project/.sdk/tm/cpp/feature/secrets/plugins/Proc.cpp +223 -0
  143. package/project/.sdk/tm/cpp/feature/secrets/plugins/Proc.hpp +47 -0
  144. package/project/.sdk/tm/cpp/feature/secrets/plugins/Secretspec.cpp +104 -0
  145. package/project/.sdk/tm/cpp/feature/secrets/plugins/Secretspec.hpp +27 -0
  146. package/project/.sdk/tm/cpp/feature/secrets/plugins/Sigv4.cpp +242 -0
  147. package/project/.sdk/tm/cpp/feature/secrets/plugins/Sigv4.hpp +58 -0
  148. package/project/.sdk/tm/cpp/feature/secrets/plugins/Tls.cpp +260 -0
  149. package/project/.sdk/tm/cpp/feature/secrets/plugins/Tls.hpp +47 -0
  150. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Json.cpp +476 -0
  151. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Json.hpp +89 -0
  152. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Provider.cpp +312 -0
  153. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Provider.hpp +263 -0
  154. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Providers.cpp +354 -0
  155. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Providers.hpp +111 -0
  156. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Sekreto.cpp +613 -0
  157. package/project/.sdk/tm/cpp/feature/secrets/sekreto/Sekreto.hpp +227 -0
  158. package/project/.sdk/tm/cpp/feature/secrets.hpp +745 -0
  159. package/project/.sdk/tm/cpp/src/feature/secrets/.gitkeep +0 -0
  160. package/project/.sdk/tm/cpp/test/feature/secrets/secrets_test.cpp +808 -0
  161. package/project/.sdk/tm/csharp/feature/SecretsFeature.cs +34 -8
  162. package/project/.sdk/tm/csharp/test/feature/secrets/SecretsFeatureTest.cs +85 -0
  163. package/project/.sdk/tm/elixir/lib/projectname/feature/secrets.ex +17 -2
  164. package/project/.sdk/tm/elixir/test/feature/secrets/secrets_test.exs +41 -0
  165. package/project/.sdk/tm/go/feature/secrets_feature.go +32 -10
  166. package/project/.sdk/tm/go/test/feature/secrets/secrets_feature_test.go +116 -0
  167. package/project/.sdk/tm/java/feature/SecretsFeature.java +33 -8
  168. package/project/.sdk/tm/java/test/feature/secrets/SecretsFeatureTest.java +64 -0
  169. package/project/.sdk/tm/js/src/feature/secrets/SecretsFeature.js +34 -11
  170. package/project/.sdk/tm/js/test/feature/secrets/Secrets.test.js +73 -0
  171. package/project/.sdk/tm/kotlin/feature/SecretsFeature.kt +32 -7
  172. package/project/.sdk/tm/kotlin/test/feature/secrets/SecretsTest.kt +69 -0
  173. package/project/.sdk/tm/lua/Makefile +15 -4
  174. package/project/.sdk/tm/lua/feature/secrets/native/sekretonet.c +806 -0
  175. package/project/.sdk/tm/lua/feature/secrets/plugin/capability.lua +135 -0
  176. package/project/.sdk/tm/lua/feature/secrets/plugin/catalog.lua +63 -0
  177. package/project/.sdk/tm/lua/feature/secrets/plugin/config.lua +361 -0
  178. package/project/.sdk/tm/lua/feature/secrets/plugin/depend.lua +232 -0
  179. package/project/.sdk/tm/lua/feature/secrets/plugin/env.lua +168 -0
  180. package/project/.sdk/tm/lua/feature/secrets/plugin/export.lua +66 -0
  181. package/project/.sdk/tm/lua/feature/secrets/plugin/graph.lua +194 -0
  182. package/project/.sdk/tm/lua/feature/secrets/plugin/host.lua +1190 -0
  183. package/project/.sdk/tm/lua/feature/secrets/plugin/json.lua +169 -0
  184. package/project/.sdk/tm/lua/feature/secrets/plugin/order.lua +207 -0
  185. package/project/.sdk/tm/lua/feature/secrets/plugin/point.lua +106 -0
  186. package/project/.sdk/tm/lua/feature/secrets/plugin/ref.lua +115 -0
  187. package/project/.sdk/tm/lua/feature/secrets/plugin/resolve.lua +78 -0
  188. package/project/.sdk/tm/lua/feature/secrets/plugin/types.lua +275 -0
  189. package/project/.sdk/tm/lua/feature/secrets/plugin/version.lua +131 -0
  190. package/project/.sdk/tm/lua/feature/secrets/plugin.lua +76 -0
  191. package/project/.sdk/tm/lua/feature/secrets/sekreto/addr.lua +124 -0
  192. package/project/.sdk/tm/lua/feature/secrets/sekreto/err.lua +47 -0
  193. package/project/.sdk/tm/lua/feature/secrets/sekreto/name.lua +315 -0
  194. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/aws.lua +228 -0
  195. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/azuresecrets.lua +137 -0
  196. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/boru.lua +124 -0
  197. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/crypto.lua +212 -0
  198. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/doppler.lua +89 -0
  199. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/gcpsecrets.lua +129 -0
  200. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/hashicorp.lua +154 -0
  201. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/httpjson.lua +248 -0
  202. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/infisical.lua +113 -0
  203. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/json.lua +449 -0
  204. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/net.lua +198 -0
  205. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/onepassword.lua +126 -0
  206. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/secretspec.lua +93 -0
  207. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/sigv4.lua +269 -0
  208. package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/support.lua +146 -0
  209. package/project/.sdk/tm/lua/feature/secrets/sekreto/providers.lua +346 -0
  210. package/project/.sdk/tm/lua/feature/secrets/sekreto.lua +479 -0
  211. package/project/.sdk/tm/lua/feature/secrets_feature.lua +690 -0
  212. package/project/.sdk/tm/lua/src/feature/secrets/.gitkeep +0 -0
  213. package/project/.sdk/tm/lua/test/extend_test.lua +105 -0
  214. package/project/.sdk/tm/lua/test/feature/secrets/secrets_feature_test.lua +862 -0
  215. package/project/.sdk/tm/ocaml/Makefile +52 -8
  216. package/project/.sdk/tm/ocaml/feature/secrets/plugin/capability.ml +106 -0
  217. package/project/.sdk/tm/ocaml/feature/secrets/plugin/catalog.ml +43 -0
  218. package/project/.sdk/tm/ocaml/feature/secrets/plugin/config.ml +349 -0
  219. package/project/.sdk/tm/ocaml/feature/secrets/plugin/defs.ml +143 -0
  220. package/project/.sdk/tm/ocaml/feature/secrets/plugin/depend.ml +217 -0
  221. package/project/.sdk/tm/ocaml/feature/secrets/plugin/env.ml +197 -0
  222. package/project/.sdk/tm/ocaml/feature/secrets/plugin/export.ml +89 -0
  223. package/project/.sdk/tm/ocaml/feature/secrets/plugin/graph.ml +227 -0
  224. package/project/.sdk/tm/ocaml/feature/secrets/plugin/host.ml +1112 -0
  225. package/project/.sdk/tm/ocaml/feature/secrets/plugin/order.ml +202 -0
  226. package/project/.sdk/tm/ocaml/feature/secrets/plugin/point.ml +138 -0
  227. package/project/.sdk/tm/ocaml/feature/secrets/plugin/ref.ml +126 -0
  228. package/project/.sdk/tm/ocaml/feature/secrets/plugin/resolve.ml +62 -0
  229. package/project/.sdk/tm/ocaml/feature/secrets/plugin/types.ml +97 -0
  230. package/project/.sdk/tm/ocaml/feature/secrets/plugin/value.ml +387 -0
  231. package/project/.sdk/tm/ocaml/feature/secrets/plugin/version.ml +139 -0
  232. package/project/.sdk/tm/ocaml/feature/secrets/plugins/aws.ml +171 -0
  233. package/project/.sdk/tm/ocaml/feature/secrets/plugins/azuresecrets.ml +116 -0
  234. package/project/.sdk/tm/ocaml/feature/secrets/plugins/boru.ml +95 -0
  235. package/project/.sdk/tm/ocaml/feature/secrets/plugins/crypto.ml +149 -0
  236. package/project/.sdk/tm/ocaml/feature/secrets/plugins/doppler.ml +61 -0
  237. package/project/.sdk/tm/ocaml/feature/secrets/plugins/gcpsecrets.ml +93 -0
  238. package/project/.sdk/tm/ocaml/feature/secrets/plugins/hashicorp.ml +120 -0
  239. package/project/.sdk/tm/ocaml/feature/secrets/plugins/http.ml +421 -0
  240. package/project/.sdk/tm/ocaml/feature/secrets/plugins/httpjson.ml +66 -0
  241. package/project/.sdk/tm/ocaml/feature/secrets/plugins/infisical.ml +85 -0
  242. package/project/.sdk/tm/ocaml/feature/secrets/plugins/onepassword.ml +112 -0
  243. package/project/.sdk/tm/ocaml/feature/secrets/plugins/runcmd.ml +97 -0
  244. package/project/.sdk/tm/ocaml/feature/secrets/plugins/secretspec.ml +68 -0
  245. package/project/.sdk/tm/ocaml/feature/secrets/plugins/sigv4.ml +259 -0
  246. package/project/.sdk/tm/ocaml/feature/secrets/plugins/tls.ml +44 -0
  247. package/project/.sdk/tm/ocaml/feature/secrets/plugins/tls_stubs.c +358 -0
  248. package/project/.sdk/tm/ocaml/feature/secrets/sekreto/json.ml +376 -0
  249. package/project/.sdk/tm/ocaml/feature/secrets/sekreto/provider.ml +647 -0
  250. package/project/.sdk/tm/ocaml/feature/secrets/sekreto/secret.ml +299 -0
  251. package/project/.sdk/tm/ocaml/feature/secrets/sekreto/sekreto.ml +347 -0
  252. package/project/.sdk/tm/ocaml/feature/secrets_feature.ml +453 -0
  253. package/project/.sdk/tm/ocaml/src/feature/secrets/.gitkeep +0 -0
  254. package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +670 -0
  255. package/project/.sdk/tm/perl/feature/secrets_feature.pm +33 -12
  256. package/project/.sdk/tm/perl/t/feature/secrets/secrets.t +47 -0
  257. package/project/.sdk/tm/php/feature/SecretsFeature.php +30 -6
  258. package/project/.sdk/tm/php/test/feature/secrets/SecretsTest.php +98 -0
  259. package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +28 -6
  260. package/project/.sdk/tm/py/test/feature/secrets/test_secrets.py +114 -0
  261. package/project/.sdk/tm/rb/feature/secrets_feature.rb +28 -6
  262. package/project/.sdk/tm/rb/test/feature/secrets/secrets_feature_test.rb +93 -0
  263. package/project/.sdk/tm/rust/feature/secrets.rs +33 -10
  264. package/project/.sdk/tm/rust/tests/feature/secrets/main.rs +96 -0
  265. package/project/.sdk/tm/scala/feature/SecretsFeature.scala +29 -7
  266. package/project/.sdk/tm/scala/sdktest/feature/secrets/SecretsTestMain.scala +71 -0
  267. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +35 -12
  268. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +73 -0
  269. package/project/.sdk/tm/zig/feature/secrets/plugin/capability.zig +137 -0
  270. package/project/.sdk/tm/zig/feature/secrets/plugin/catalog.zig +90 -0
  271. package/project/.sdk/tm/zig/feature/secrets/plugin/config.zig +346 -0
  272. package/project/.sdk/tm/zig/feature/secrets/plugin/depend.zig +244 -0
  273. package/project/.sdk/tm/zig/feature/secrets/plugin/env.zig +201 -0
  274. package/project/.sdk/tm/zig/feature/secrets/plugin/export.zig +83 -0
  275. package/project/.sdk/tm/zig/feature/secrets/plugin/graph.zig +212 -0
  276. package/project/.sdk/tm/zig/feature/secrets/plugin/host.zig +1224 -0
  277. package/project/.sdk/tm/zig/feature/secrets/plugin/inst.zig +126 -0
  278. package/project/.sdk/tm/zig/feature/secrets/plugin/order.zig +228 -0
  279. package/project/.sdk/tm/zig/feature/secrets/plugin/plugin.zig +34 -0
  280. package/project/.sdk/tm/zig/feature/secrets/plugin/point.zig +166 -0
  281. package/project/.sdk/tm/zig/feature/secrets/plugin/ref.zig +147 -0
  282. package/project/.sdk/tm/zig/feature/secrets/plugin/resolve.zig +74 -0
  283. package/project/.sdk/tm/zig/feature/secrets/plugin/types.zig +140 -0
  284. package/project/.sdk/tm/zig/feature/secrets/plugin/value.zig +662 -0
  285. package/project/.sdk/tm/zig/feature/secrets/plugin/version.zig +139 -0
  286. package/project/.sdk/tm/zig/feature/secrets/plugins/aws.zig +294 -0
  287. package/project/.sdk/tm/zig/feature/secrets/plugins/azuresecrets.zig +222 -0
  288. package/project/.sdk/tm/zig/feature/secrets/plugins/boru.zig +188 -0
  289. package/project/.sdk/tm/zig/feature/secrets/plugins/doppler.zig +135 -0
  290. package/project/.sdk/tm/zig/feature/secrets/plugins/gcpsecrets.zig +170 -0
  291. package/project/.sdk/tm/zig/feature/secrets/plugins/hashicorp.zig +242 -0
  292. package/project/.sdk/tm/zig/feature/secrets/plugins/httpjson.zig +535 -0
  293. package/project/.sdk/tm/zig/feature/secrets/plugins/infisical.zig +167 -0
  294. package/project/.sdk/tm/zig/feature/secrets/plugins/onepassword.zig +202 -0
  295. package/project/.sdk/tm/zig/feature/secrets/plugins/secretspec.zig +160 -0
  296. package/project/.sdk/tm/zig/feature/secrets/plugins/sigv4.zig +336 -0
  297. package/project/.sdk/tm/zig/feature/secrets/sekreto/addr.zig +148 -0
  298. package/project/.sdk/tm/zig/feature/secrets/sekreto/builtins.zig +259 -0
  299. package/project/.sdk/tm/zig/feature/secrets/sekreto/provider.zig +440 -0
  300. package/project/.sdk/tm/zig/feature/secrets/sekreto/sekreto.zig +944 -0
  301. package/project/.sdk/tm/zig/feature/secrets.zig +854 -0
  302. package/project/.sdk/tm/zig/src/feature/secrets/.gitkeep +0 -0
  303. package/project/.sdk/tm/zig/test/feature/secrets/secrets_test.zig +891 -0
  304. package/project/sdkgen-package.json +1 -1
@@ -0,0 +1,802 @@
1
+ // Behavioural tests for the secrets feature (vendored @voxgig/sekreto) -
2
+ // the c port of tm/ts/test/feature/secrets/Secrets.test.ts, in the shape
3
+ // of tm/js/test/feature/secrets/Secrets.test.js and kotlin's SecretsTest.
4
+ //
5
+ // The contract under test: the `apikey` OPTION keeps its exact old meaning
6
+ // and always wins, because the feature places it FIRST in the provider
7
+ // chain (a `memory` store named `options`) - explicit-beats-lookup falls
8
+ // out of sekreto's first-hit rule rather than from special-case logic. With
9
+ // the feature inactive nothing changes at all. With it active and the
10
+ // option unset, the chain (a memory store, a custom provider, a vault)
11
+ // supplies the credential instead.
12
+ //
13
+ // This file lives in the tests/feature/ container on purpose: `target add`
14
+ // trims it, along with the feature source and the vendored library, for a
15
+ // project whose model does not select `secrets`; the Makefile compiles it
16
+ // only when the feature is wired in (its generated kinds.mk exists).
17
+ //
18
+ // THE CLIENT IS LIVE AND THE TRANSPORT IS THE THING COUNTED. Every wire
19
+ // assertion here runs against a real client whose `options.system.fetch` is
20
+ // the recorder below - never `test_sdk`, whose test feature REPLACES the
21
+ // fetcher with its own in-memory mock and would leave a system.fetch
22
+ // counter at zero for a healthy SDK carrying no secrets feature at all. An
23
+ // assertion that cannot fail pins no rule, so each fail-closed case carries
24
+ // a CONTROL leg: the same construction with a WORKING provider must reach
25
+ // the same recorder exactly once, carrying the credential. Only then does
26
+ // a zero from the broken provider mean REFUSED rather than UNWIRED. And the
27
+ // refusal is matched on the PROVIDER'S OWN message, so an unrelated failure
28
+ // (a blocked op, a missing route) cannot stand in for fail-closed.
29
+ //
30
+ // Both raw paths (sdk_direct, sdk_graphql) are driven, because they run no
31
+ // feature hooks at all and are guarded ONLY by the transport wrapper. The
32
+ // entity pipeline is driven through the feature harness (feature_harness.h
33
+ // fh_op - the same miniature pipeline every other c feature test uses,
34
+ // since a template cannot name an entity), whose recorder is the Fetcher
35
+ // the wrapper wraps.
36
+ //
37
+ // NO ENVIRONMENT VARIABLES: the ts/go suites' `env` chains become `memory`
38
+ // chains and `custom` providers here. The rule under test - the chain
39
+ // answers when the option does not - is identical either way.
40
+
41
+ #include "feature_harness.h" /* test_sdk + Fetcher helpers + ctest.h */
42
+
43
+ #include "secrets.h"
44
+ #include "sekreto.h"
45
+
46
+ #include <stdio.h>
47
+ #include <stdlib.h>
48
+ #include <string.h>
49
+
50
+ static int CASES = 0;
51
+ #define RUN(fn) \
52
+ do { \
53
+ CASES++; \
54
+ fn(); \
55
+ } while (0)
56
+
57
+ // ---- the recording live transport ------------------------------------------
58
+
59
+ #define MAX_CALLS 16
60
+
61
+ typedef struct {
62
+ // Every call, API and token endpoint alike: { url, fetchdef }.
63
+ voxgig_value* calls;
64
+ int api; // API calls seen
65
+ int token; // token-endpoint calls seen
66
+ // Status script for API calls, by API call index; 0 means 200.
67
+ int64_t script[MAX_CALLS];
68
+ // Tokens the endpoint answers, by token call index; NULL means "ACCESS0N".
69
+ const char* tokens[MAX_CALLS];
70
+ int64_t tokenstatus; // 0 means 200
71
+ const char* tokenpath; // substring that marks the token endpoint
72
+ const char* tokenfield; // the response field name
73
+ } Rec;
74
+
75
+ static Rec* rec_new(void) {
76
+ Rec* r = (Rec*)calloc(1, sizeof(Rec));
77
+ r->calls = v_list();
78
+ r->tokenpath = "/auth/token";
79
+ r->tokenfield = "access_token";
80
+ return r;
81
+ }
82
+
83
+ static voxgig_value* respond(int64_t status, voxgig_value* data) {
84
+ return cmap(5,
85
+ "status", v_num((double)status),
86
+ "statusText", v_str(status >= 400 ? "ERR" : "OK"),
87
+ "headers", v_map(),
88
+ "json", json_thunk(data),
89
+ "body", v_str("not-used"));
90
+ }
91
+
92
+ // args = [url, fetchdef], the system.fetch calling convention.
93
+ static voxgig_value* live_fetch(void* ud, voxgig_value* args) {
94
+ Rec* r = (Rec*)ud;
95
+ voxgig_value* urlv = voxgig_getelem(args, v_int(0), voxgig_new_undef());
96
+ voxgig_value* fetchdef = voxgig_getelem(args, v_int(1), voxgig_new_undef());
97
+ const char* url = voxgig_is_string(urlv) ? voxgig_as_string(urlv) : "";
98
+ // A SNAPSHOT, not a share: the exchange rewrites the authorization header
99
+ // of the SAME fetchdef in place before it retries, so a shared reference
100
+ // would show the retry's token on the first attempt's record.
101
+ voxgig_list_push(voxgig_as_list(r->calls),
102
+ cmap(2, "url", v_str(url), "fetchdef", voxgig_clone(fetchdef)));
103
+
104
+ if (NULL != strstr(url, r->tokenpath)) {
105
+ int n = r->token++;
106
+ char buf[32];
107
+ snprintf(buf, sizeof(buf), "ACCESS0%d", n + 1);
108
+ const char* tok = (n < MAX_CALLS && r->tokens[n]) ? r->tokens[n] : buf;
109
+ int64_t st = 0 == r->tokenstatus ? 200 : r->tokenstatus;
110
+ return respond(st, cmap(1, r->tokenfield, v_str(tok)));
111
+ }
112
+
113
+ int n = r->api++;
114
+ int64_t st = (n < MAX_CALLS && 0 != r->script[n]) ? r->script[n] : 200;
115
+ return respond(st, cmap(2, "ok", v_bool(true), "n", v_num((double)n + 1)));
116
+ }
117
+
118
+ static voxgig_value* call_fetchdef(Rec* r, int i) {
119
+ return getp(voxgig_getelem(r->calls, v_int(i), voxgig_new_undef()), "fetchdef");
120
+ }
121
+
122
+ // The authorization header of call i, or NULL when absent. Presence is read
123
+ // off the map directly: a present-but-EMPTY header is not suppression, and
124
+ // get_str alone cannot tell the two apart.
125
+ static const char* call_auth(Rec* r, int i, bool* present) {
126
+ voxgig_value* headers = getp(call_fetchdef(r, i), "headers");
127
+ if (present) {
128
+ *present = voxgig_is_map(headers) &&
129
+ NULL != voxgig_map_get(voxgig_as_map(headers), "authorization");
130
+ }
131
+ return get_str(headers, "authorization");
132
+ }
133
+
134
+ // The Authorization header carries the SPEC's credential prefix, which a
135
+ // TEMPLATE cannot know (a bearer scheme gives `Bearer <token>`, an apiKey
136
+ // scheme the raw token). So assert on the CREDENTIAL and let the prefix be
137
+ // whatever this SDK's API declares.
138
+ static bool credential_is(const char* header, const char* token) {
139
+ if (NULL == header) return false;
140
+ size_t hl = strlen(header);
141
+ size_t tl = strlen(token);
142
+ if (0 == strcmp(header, token)) return true;
143
+ return hl > tl + 1 && ' ' == header[hl - tl - 1] && 0 == strcmp(header + hl - tl, token);
144
+ }
145
+
146
+ // ---- a LIVE client ----------------------------------------------------------
147
+
148
+ // A live-mode client whose system.fetch is the recorder. `secretsopts` is
149
+ // the feature's option map (active is forced true unless the map says
150
+ // otherwise); `extra` entries are merged into the top-level options
151
+ // (apikey, auth, ...).
152
+ static ProjectNameSDK* live(Rec* r, voxgig_value* secretsopts, voxgig_value* extra) {
153
+ voxgig_value* fopts = voxgig_is_map(secretsopts) ? secretsopts : v_map();
154
+ if (v_is_noval(getp(fopts, "active"))) setp(fopts, "active", v_bool(true));
155
+ voxgig_value* opts = cmap(3,
156
+ "base", v_str("http://api.test"),
157
+ "system", cmap(1, "fetch", vfn(live_fetch, r)),
158
+ "feature", cmap(1, "secrets", fopts));
159
+ if (voxgig_is_map(extra)) {
160
+ voxgig_map* m = voxgig_as_map(extra);
161
+ for (size_t i = 0; i < m->len; i++) setp(opts, m->entries[i].key, m->entries[i].value);
162
+ }
163
+ return projectname_sdk_new(opts);
164
+ }
165
+
166
+ static voxgig_value* direct(ProjectNameSDK* sdk) {
167
+ PNError* err = NULL;
168
+ voxgig_value* res = sdk_direct(sdk, cmap(2, "path", v_str("/thing"), "method", v_str("GET")), &err);
169
+ CHECK(NULL == err, "sdk_direct never sets *err for a refused request");
170
+ return res;
171
+ }
172
+
173
+ static bool res_ok(voxgig_value* res) {
174
+ voxgig_value* okv = getp(res, "ok");
175
+ return voxgig_is_bool(okv) && voxgig_as_bool(okv);
176
+ }
177
+
178
+ static const char* res_err(voxgig_value* res) {
179
+ const char* e = get_str(res, "err");
180
+ return e ? e : "";
181
+ }
182
+
183
+ // ---- providers --------------------------------------------------------------
184
+
185
+ static voxgig_value* memory(const char* name, const char* key, const char* value) {
186
+ voxgig_value* spec = cmap(2, "kind", v_str("memory"), "values", cmap(1, key, v_str(value)));
187
+ if (name) setp(spec, "name", v_str(name));
188
+ return spec;
189
+ }
190
+
191
+ // A scripted custom provider: counts lookups, records the last name asked,
192
+ // fails the first `failfirst` lookups with its own message, then answers
193
+ // `value` (a miss when NULL).
194
+ typedef struct {
195
+ int asked;
196
+ char lastname[128];
197
+ int failfirst;
198
+ const char* value;
199
+ const char* message;
200
+ } Custom;
201
+
202
+ static voxgig_value* custom_lookup(void* ud, voxgig_value* arg) {
203
+ Custom* c = (Custom*)ud;
204
+ c->asked++;
205
+ snprintf(c->lastname, sizeof(c->lastname), "%s",
206
+ voxgig_is_string(arg) ? voxgig_as_string(arg) : "(not a name)");
207
+ if (c->asked <= c->failfirst) {
208
+ return cmap(1, "__err__", v_str(c->message ? c->message : "vault unreachable"));
209
+ }
210
+ if (NULL == c->value) return v_undef();
211
+ return v_str(c->value);
212
+ }
213
+
214
+ static Custom* custom_new(const char* value, int failfirst) {
215
+ Custom* c = (Custom*)calloc(1, sizeof(Custom));
216
+ c->value = value;
217
+ c->failfirst = failfirst;
218
+ return c;
219
+ }
220
+
221
+ static voxgig_value* custom_spec(Custom* c, const char* name) {
222
+ voxgig_value* spec = cmap(2, "kind", v_str("custom"), "lookup", vfn(custom_lookup, c));
223
+ if (name) setp(spec, "name", v_str(name));
224
+ return spec;
225
+ }
226
+
227
+ static voxgig_value* providers1(voxgig_value* a) { return cmap(1, "providers", clist(1, a)); }
228
+
229
+ // ---- inactive ---------------------------------------------------------------
230
+
231
+ static void t_inactive_apikey_as_before(void) {
232
+ Rec* r = rec_new();
233
+ ProjectNameSDK* sdk = live(r, cmap(1, "active", v_bool(false)),
234
+ cmap(1, "apikey", v_str("OPTKEY01")));
235
+ CHECK(NULL == sdk_feature_secrets(sdk), "inactive: no secrets feature is installed");
236
+ voxgig_value* res = direct(sdk);
237
+ CHECK(res_ok(res), "inactive: the request succeeds");
238
+ CHECK_INT_EQ(r->api, 1, "inactive: the recorder IS the wire (control)");
239
+ CHECK(credential_is(call_auth(r, 0, NULL), "OPTKEY01"), "inactive: apikey option is sent as before");
240
+ }
241
+
242
+ static void t_inactive_no_apikey_no_header(void) {
243
+ Rec* r = rec_new();
244
+ ProjectNameSDK* sdk = live(r, cmap(1, "active", v_bool(false)), v_map());
245
+ direct(sdk);
246
+ bool present = true;
247
+ call_auth(r, 0, &present);
248
+ CHECK_INT_EQ(r->api, 1, "inactive: one call");
249
+ CHECK(!present, "inactive: no apikey means no authorization header");
250
+ }
251
+
252
+ // ---- active: the chain ------------------------------------------------------
253
+
254
+ static void t_apikey_wins_over_chain(void) {
255
+ Rec* r = rec_new();
256
+ ProjectNameSDK* sdk = live(r, providers1(memory("store", "APIKEY", "CHAIN01")),
257
+ cmap(1, "apikey", v_str("OPTKEY01")));
258
+ Feature* f = sdk_feature_secrets(sdk);
259
+ CHECK(NULL != f, "active: the feature is installed");
260
+ CHECK(NULL == feature_secrets_initerr(f), "active: the chain built");
261
+ direct(sdk);
262
+ CHECK_INT_EQ(r->api, 1, "active: one call");
263
+ CHECK(credential_is(call_auth(r, 0, NULL), "OPTKEY01"), "active: the apikey option wins over the chain");
264
+
265
+ // The seat is a REAL store, not a special case: a directed read finds it.
266
+ sek_sekreto* sek = feature_secrets_sekreto(f);
267
+ char* got = NULL;
268
+ sek_err e = sek_getfrom(sek, "options", "apikey", &got);
269
+ CHECK(NULL == e, "active: the `options` store can be read directly");
270
+ CHECK_STR_EQ(got, "OPTKEY01", "active: the `options` store holds the option");
271
+ }
272
+
273
+ static void t_omitted_apikey_defers(void) {
274
+ Rec* r = rec_new();
275
+ ProjectNameSDK* sdk = live(r, providers1(memory(NULL, "APIKEY", "CHAIN01")), v_map());
276
+ direct(sdk);
277
+ CHECK(credential_is(call_auth(r, 0, NULL), "CHAIN01"), "active: an OMITTED apikey defers to the chain");
278
+ // The options map is FROZEN: the feature never writes the credential
279
+ // into it, so the raw paths and the entity path agree by construction.
280
+ voxgig_value* opts = sdk_options_map(sdk);
281
+ const char* held = get_str(opts, "apikey");
282
+ CHECK(NULL == held || '\0' == held[0], "active: options.apikey is never written by the feature");
283
+ CHECK_STR_EQ(feature_secrets_credential(sdk_feature_secrets(sdk)), "CHAIN01",
284
+ "active: the resolved credential is held by the feature");
285
+ }
286
+
287
+ static void t_empty_apikey_defers(void) {
288
+ Rec* r = rec_new();
289
+ ProjectNameSDK* sdk = live(r, providers1(memory(NULL, "APIKEY", "CHAIN01")),
290
+ cmap(1, "apikey", v_str("")));
291
+ direct(sdk);
292
+ CHECK(credential_is(call_auth(r, 0, NULL), "CHAIN01"), "active: an explicitly EMPTY apikey also defers");
293
+ }
294
+
295
+ static void t_auth_null_suppresses(void) {
296
+ // Chain AND explicit apikey both set: suppression beats both.
297
+ Rec* r = rec_new();
298
+ ProjectNameSDK* sdk = live(r, providers1(memory(NULL, "APIKEY", "CHAIN01")),
299
+ cmap(2, "apikey", v_str("OPTKEY01"), "auth", voxgig_new_null()));
300
+ voxgig_value* res = direct(sdk);
301
+ CHECK(res_ok(res), "auth null: the request still goes out");
302
+ CHECK_INT_EQ(r->api, 1, "auth null: one call");
303
+ bool present = true;
304
+ call_auth(r, 0, &present);
305
+ CHECK(!present, "auth null: suppresses the credential, chain or no chain");
306
+ // ...and the suppression survives make_options as a present null.
307
+ voxgig_value* opts = sdk_options_map(sdk);
308
+ voxgig_value* auth = voxgig_map_get(voxgig_as_map(opts), "auth");
309
+ CHECK(NULL != auth && voxgig_is_null(auth), "auth null: options.auth survives as a present null");
310
+ }
311
+
312
+ static void t_custom_provider_verbatim(void) {
313
+ // Both spellings: a bare FUNC, and the `custom` pseudo-kind map with a
314
+ // store name a directed read can address.
315
+ Rec* r = rec_new();
316
+ Custom* bare = custom_new(NULL, 0);
317
+ Custom* named = custom_new("CUSTOM01", 0);
318
+ ProjectNameSDK* sdk = live(r,
319
+ cmap(1, "providers", clist(2, vfn(custom_lookup, bare), custom_spec(named, "mine"))),
320
+ v_map());
321
+ Feature* f = sdk_feature_secrets(sdk);
322
+ CHECK(NULL == feature_secrets_initerr(f), "custom: both entries joined the chain");
323
+ direct(sdk);
324
+ CHECK(credential_is(call_auth(r, 0, NULL), "CUSTOM01"), "custom: the provider's value reaches the wire");
325
+ CHECK_STR_EQ(named->lastname, "apikey", "custom: asked for the configured secret name");
326
+ CHECK_INT_EQ(bare->asked, 1, "custom: the bare FUNC was asked first (a miss, so the chain went on)");
327
+ char* got = NULL;
328
+ sek_err e = sek_getfrom(feature_secrets_sekreto(f), "mine", "apikey", &got);
329
+ CHECK(NULL == e && NULL != got && 0 == strcmp(got, "CUSTOM01"), "custom: addressable by its store name");
330
+ }
331
+
332
+ static void t_miss_everywhere_no_header(void) {
333
+ Rec* r = rec_new();
334
+ Custom* c = custom_new(NULL, 0);
335
+ ProjectNameSDK* sdk = live(r, providers1(custom_spec(c, NULL)), v_map());
336
+ voxgig_value* res = direct(sdk);
337
+ CHECK(res_ok(res), "miss: the op still runs");
338
+ CHECK_INT_EQ(r->api, 1, "miss: one call");
339
+ bool present = true;
340
+ call_auth(r, 0, &present);
341
+ CHECK(!present, "miss: a miss everywhere leaves the header off");
342
+ CHECK_INT_EQ(c->asked, 1, "miss: the chain was asked");
343
+ }
344
+
345
+ // ---- active: fail closed ----------------------------------------------------
346
+
347
+ static void t_provider_error_fails_direct(void) {
348
+ Rec* r = rec_new();
349
+ Custom* broken = custom_new("NEVER", 99);
350
+ broken->message = "vault unreachable";
351
+ ProjectNameSDK* sdk = live(r, providers1(custom_spec(broken, NULL)), v_map());
352
+ voxgig_value* res = direct(sdk);
353
+ CHECK(!res_ok(res), "error: direct() fails");
354
+ CHECK(NULL != strstr(res_err(res), "vault unreachable"), "error: direct() carries the PROVIDER'S OWN message");
355
+ CHECK_INT_EQ(r->api, 0, "error: nothing reached the wire");
356
+
357
+ // CONTROL: the same construction with a working provider reaches the
358
+ // same recorder exactly once, carrying the credential.
359
+ Rec* r2 = rec_new();
360
+ ProjectNameSDK* ok = live(r2, providers1(custom_spec(custom_new("CUSTOM01", 0), NULL)), v_map());
361
+ CHECK(res_ok(direct(ok)), "control: the working chain succeeds");
362
+ CHECK_INT_EQ(r2->api, 1, "control: the working chain reached the wire once");
363
+ CHECK(credential_is(call_auth(r2, 0, NULL), "CUSTOM01"), "control: with the credential");
364
+ }
365
+
366
+ static voxgig_value* graphql(ProjectNameSDK* sdk) {
367
+ PNError* err = NULL;
368
+ voxgig_value* res = sdk_graphql(sdk, "{ thing { id } }", v_map(), v_map(), &err);
369
+ CHECK(NULL == err, "sdk_graphql never sets *err for a refused request");
370
+ return res;
371
+ }
372
+
373
+ static void t_provider_error_fails_graphql(void) {
374
+ Rec* r = rec_new();
375
+ Custom* broken = custom_new("NEVER", 99);
376
+ broken->message = "vault unreachable";
377
+ ProjectNameSDK* sdk = live(r, providers1(custom_spec(broken, NULL)), v_map());
378
+ voxgig_value* res = graphql(sdk);
379
+ CHECK(!res_ok(res), "error: graphql() fails");
380
+ CHECK(NULL != strstr(res_err(res), "vault unreachable"), "error: graphql() carries the provider's own message");
381
+ CHECK_INT_EQ(r->api, 0, "error: graphql sent nothing");
382
+
383
+ Rec* r2 = rec_new();
384
+ ProjectNameSDK* ok = live(r2, providers1(custom_spec(custom_new("CUSTOM01", 0), NULL)), v_map());
385
+ CHECK(res_ok(graphql(ok)), "control: graphql with a working chain succeeds");
386
+ CHECK_INT_EQ(r2->api, 1, "control: graphql reached the wire once");
387
+ CHECK(credential_is(call_auth(r2, 0, NULL), "CUSTOM01"), "control: graphql carried the credential");
388
+ }
389
+
390
+ // The ENTITY pipeline, through the harness's miniature of it: the wrapper
391
+ // wraps the harness recorder, so a refusal here is the refusal every
392
+ // generated entity op gets from make_request.
393
+ static void t_provider_error_fails_entity_op(void) {
394
+ voxgig_value* calls = NULL;
395
+ Fetcher* server = fh_recorder(NULL, NULL, &calls);
396
+ Custom* broken = custom_new("NEVER", 99);
397
+ broken->message = "vault unreachable";
398
+ FhFeat feats[1] = {{feature_secrets_new(), providers1(custom_spec(broken, NULL))}};
399
+ FhHarness h = fh_make(server, feats, 1);
400
+ FhOpSpec o;
401
+ memset(&o, 0, sizeof(o));
402
+ FhOpResult res = fh_op(&h, o);
403
+ CHECK(!res.ok, "entity: the op fails");
404
+ CHECK(NULL != res.err && NULL != res.err->msg && NULL != strstr(res.err->msg, "vault unreachable"),
405
+ "entity: the op carries the provider's own message");
406
+ CHECK_INT_EQ(rec_count(calls), 0, "entity: nothing reached the transport");
407
+
408
+ voxgig_value* calls2 = NULL;
409
+ Fetcher* server2 = fh_recorder(NULL, NULL, &calls2);
410
+ FhFeat feats2[1] = {{feature_secrets_new(), providers1(custom_spec(custom_new("CUSTOM01", 0), NULL))}};
411
+ FhHarness h2 = fh_make(server2, feats2, 1);
412
+ FhOpResult res2 = fh_op(&h2, o);
413
+ CHECK(res2.ok, "control: the entity op with a working chain succeeds");
414
+ CHECK_INT_EQ(rec_count(calls2), 1, "control: the entity op reached the transport once");
415
+ CHECK(credential_is(get_str(rec_headers(calls2, 0), "authorization"), "CUSTOM01"),
416
+ "control: the entity op carried the credential");
417
+ }
418
+
419
+ static void t_recovers_after_transient_failure(void) {
420
+ Rec* r = rec_new();
421
+ Custom* flaky = custom_new("CUSTOM01", 1);
422
+ ProjectNameSDK* sdk = live(r, providers1(custom_spec(flaky, NULL)), v_map());
423
+ voxgig_value* first = direct(sdk);
424
+ CHECK(!res_ok(first), "recover: the first request is refused");
425
+ CHECK_INT_EQ(r->api, 0, "recover: nothing sent on the failure");
426
+ voxgig_value* second = direct(sdk);
427
+ CHECK(res_ok(second), "recover: a failed resolution is never cached - the next op asks again");
428
+ CHECK_INT_EQ(r->api, 1, "recover: the second request went out");
429
+ CHECK(credential_is(call_auth(r, 0, NULL), "CUSTOM01"), "recover: with the credential");
430
+ }
431
+
432
+ static void t_cache_false_asks_every_time(void) {
433
+ Rec* r = rec_new();
434
+ Custom* c = custom_new("CUSTOM01", 0);
435
+ voxgig_value* fopts = providers1(custom_spec(c, NULL));
436
+ setp(fopts, "cache", v_bool(false));
437
+ ProjectNameSDK* sdk = live(r, fopts, v_map());
438
+ direct(sdk);
439
+ direct(sdk);
440
+ CHECK_INT_EQ(c->asked, 2, "cache false: the chain is asked on every request");
441
+
442
+ Rec* r2 = rec_new();
443
+ Custom* c2 = custom_new("CUSTOM01", 0);
444
+ ProjectNameSDK* cached = live(r2, providers1(custom_spec(c2, NULL)), v_map());
445
+ direct(cached);
446
+ direct(cached);
447
+ CHECK_INT_EQ(c2->asked, 1, "cache true (default): the chain is asked once");
448
+ CHECK_INT_EQ(r2->api, 2, "cache true: both requests went out");
449
+ }
450
+
451
+ static void t_cache_false_miss_retracts(void) {
452
+ Rec* r = rec_new();
453
+ Custom* c = custom_new("CUSTOM01", 0);
454
+ voxgig_value* fopts = providers1(custom_spec(c, NULL));
455
+ setp(fopts, "cache", v_bool(false));
456
+ ProjectNameSDK* sdk = live(r, fopts, v_map());
457
+ direct(sdk);
458
+ CHECK(credential_is(call_auth(r, 0, NULL), "CUSTOM01"), "retract: the first request carries the hit");
459
+ c->value = NULL; // the store no longer holds it
460
+ direct(sdk);
461
+ bool present = true;
462
+ call_auth(r, 1, &present);
463
+ CHECK(!present, "retract: an uncached miss after a hit takes the credential off the wire");
464
+ CHECK_STR_EQ(feature_secrets_credential(sdk_feature_secrets(sdk)), "", "retract: the held credential is cleared");
465
+ }
466
+
467
+ static void t_secret_name_configurable(void) {
468
+ Rec* r = rec_new();
469
+ Custom* c = custom_new("TOKEN01", 0);
470
+ voxgig_value* fopts = providers1(custom_spec(c, NULL));
471
+ setp(fopts, "name", v_str("api.token"));
472
+ ProjectNameSDK* sdk = live(r, fopts, v_map());
473
+ direct(sdk);
474
+ CHECK_STR_EQ(c->lastname, "api.token", "name: the configured secret name is what the chain is asked for");
475
+ CHECK(credential_is(call_auth(r, 0, NULL), "TOKEN01"), "name: and its value is the credential");
476
+ }
477
+
478
+ static void t_sekreto_is_live(void) {
479
+ Rec* r = rec_new();
480
+ ProjectNameSDK* sdk = live(r,
481
+ providers1(cmap(2, "kind", v_str("memory"),
482
+ "values", cmap(2, "APIKEY", v_str("CHAIN01"), "OTHER_SECRET", v_str("sesame-open")))),
483
+ v_map());
484
+ sek_sekreto* sek = feature_secrets_sekreto(sdk_feature_secrets(sdk));
485
+ CHECK(NULL != sek, "live: the sekreto instance is exposed");
486
+ char* got = NULL;
487
+ sek_err e = sek_get(sek, "other.secret", &got);
488
+ CHECK(NULL == e && NULL != got && 0 == strcmp(got, "sesame-open"), "live: arbitrary secrets resolve");
489
+ char* red = sek_redact_text(sek, "the value sesame-open was logged");
490
+ CHECK_STR_EQ(red, "the value [redacted] was logged", "live: redaction knows every resolved value");
491
+ }
492
+
493
+ static void t_unknown_kind_refused(void) {
494
+ Rec* r = rec_new();
495
+ ProjectNameSDK* sdk = live(r, providers1(cmap(1, "kind", v_str("nosuchkind"))), v_map());
496
+ Feature* f = sdk_feature_secrets(sdk);
497
+ const char* ie = feature_secrets_initerr(f);
498
+ CHECK(NULL != ie && NULL != strstr(ie, "unknown provider kind: nosuchkind"),
499
+ "init: an unknown kind is refused with sekreto's own message");
500
+ voxgig_value* res = direct(sdk);
501
+ CHECK(!res_ok(res), "init: the request is refused");
502
+ CHECK(NULL != strstr(res_err(res), "unknown provider kind"), "init: the refusal carries sekreto's message");
503
+ CHECK_INT_EQ(r->api, 0, "init: nothing reached the wire");
504
+ CHECK(res_ok(graphql(sdk)) == false && r->api == 0, "init: graphql is refused too");
505
+
506
+ Rec* r2 = rec_new();
507
+ ProjectNameSDK* ok = live(r2, providers1(memory(NULL, "APIKEY", "CHAIN01")), v_map());
508
+ CHECK(res_ok(direct(ok)) && 1 == r2->api, "control: a known kind reaches the wire once");
509
+ }
510
+
511
+ static void t_malformed_entry_never_dropped(void) {
512
+ // A bare kind name where a spec map was meant, AFTER a working store: the
513
+ // chain must not be quietly shortened to the working store.
514
+ Rec* r = rec_new();
515
+ ProjectNameSDK* sdk = live(r,
516
+ cmap(1, "providers", clist(2, memory(NULL, "APIKEY", "CHAIN01"), v_str("hashicorp"))),
517
+ v_map());
518
+ const char* ie = feature_secrets_initerr(sdk_feature_secrets(sdk));
519
+ CHECK(NULL != ie && NULL != strstr(ie, "not a provider or a provider spec"),
520
+ "malformed: a bare string entry is refused, not dropped");
521
+ voxgig_value* res = direct(sdk);
522
+ CHECK(!res_ok(res) && NULL != strstr(res_err(res), "not a provider or a provider spec"),
523
+ "malformed: the request is refused with the message");
524
+ CHECK_INT_EQ(r->api, 0, "malformed: nothing sent");
525
+
526
+ // A number, and a `custom` map with no callable.
527
+ Rec* r2 = rec_new();
528
+ ProjectNameSDK* num = live(r2, providers1(v_num(42)), v_map());
529
+ CHECK(!res_ok(direct(num)) && 0 == r2->api, "malformed: a numeric entry is refused");
530
+ Rec* r3 = rec_new();
531
+ ProjectNameSDK* nolookup = live(r3, providers1(cmap(1, "kind", v_str("custom"))), v_map());
532
+ CHECK(!res_ok(direct(nolookup)) && 0 == r3->api, "malformed: a custom entry without a lookup is refused");
533
+
534
+ Rec* r4 = rec_new();
535
+ ProjectNameSDK* ok = live(r4, providers1(memory(NULL, "APIKEY", "CHAIN01")), v_map());
536
+ CHECK(res_ok(direct(ok)) && 1 == r4->api, "control: the well-formed store alone reaches the wire once");
537
+ }
538
+
539
+ static void t_selected_kinds_in_vocabulary(void) {
540
+ // The definitions the model selected are what the chain can build. This
541
+ // file cannot know which groups a project chose, so it checks the
542
+ // contract from both sides: every selected definition is in the live
543
+ // catalog, and a kind NOT selected is refused by name (see the unknown
544
+ // kind case). With no group selected the list is empty and only the
545
+ // four built-ins remain.
546
+ size_t n = 0;
547
+ void** defs = feature_plugins("secrets", &n);
548
+ Rec* r = rec_new();
549
+ ProjectNameSDK* sdk = live(r, providers1(memory(NULL, "APIKEY", "CHAIN01")), v_map());
550
+ Catalog* cat = sek_catalog(feature_secrets_sekreto(sdk_feature_secrets(sdk)));
551
+ CHECK(catalog_has(cat, "memory") && catalog_has(cat, "env"), "vocabulary: the built-ins are always there");
552
+ for (size_t i = 0; i < n; i++) {
553
+ Definition* d = (Definition*)defs[i];
554
+ CHECK(NULL != d && NULL != d->name, "vocabulary: a selected definition is a real one");
555
+ CHECK(catalog_has(cat, d->name), "vocabulary: a selected kind is in the live catalog");
556
+ }
557
+ printf("secrets: %zu plugin definition(s) selected by the model\n", n);
558
+ CHECK(0 == n || NULL != defs, "vocabulary: a non-empty count comes with a list");
559
+ }
560
+
561
+ static void t_raw_paths_get_chain_credential(void) {
562
+ Rec* r = rec_new();
563
+ ProjectNameSDK* sdk = live(r, providers1(memory(NULL, "APIKEY", "CHAIN01")), v_map());
564
+ CHECK(res_ok(direct(sdk)), "raw: direct succeeds");
565
+ CHECK(res_ok(graphql(sdk)), "raw: graphql succeeds");
566
+ CHECK_INT_EQ(r->api, 2, "raw: both raw paths reached the wire");
567
+ CHECK(credential_is(call_auth(r, 0, NULL), "CHAIN01"), "raw: direct carries the chain credential");
568
+ CHECK(credential_is(call_auth(r, 1, NULL), "CHAIN01"), "raw: graphql carries the chain credential");
569
+ }
570
+
571
+ // ---- the exchange -----------------------------------------------------------
572
+
573
+ static voxgig_value* exchange_opts(voxgig_value* providers, voxgig_value* xextra) {
574
+ voxgig_value* x = cmap(1, "active", v_bool(true));
575
+ if (voxgig_is_map(xextra)) {
576
+ voxgig_map* m = voxgig_as_map(xextra);
577
+ for (size_t i = 0; i < m->len; i++) setp(x, m->entries[i].key, m->entries[i].value);
578
+ }
579
+ return cmap(2, "providers", providers, "exchange", x);
580
+ }
581
+
582
+ static voxgig_value* refresh_store(void) { return clist(1, memory(NULL, "APIKEY", "REFRESH01")); }
583
+
584
+ static const char* call_body(Rec* r, int i) {
585
+ const char* b = get_str(call_fetchdef(r, i), "body");
586
+ return b ? b : "";
587
+ }
588
+
589
+ static void t_exchange_buys_and_carries(void) {
590
+ Rec* r = rec_new();
591
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), v_map());
592
+ CHECK(res_ok(direct(sdk)), "exchange: the request succeeds");
593
+ CHECK_INT_EQ(r->token, 1, "exchange: one token purchase");
594
+ CHECK_INT_EQ(r->api, 1, "exchange: one API call");
595
+ CHECK(NULL != strstr(call_body(r, 0), "\"refresh_token\":\"REFRESH01\""), "exchange: the purchase carries the refresh token, marshalled");
596
+ CHECK(credential_is(call_auth(r, 1, NULL), "ACCESS01"), "exchange: the request carries the bought access token");
597
+ CHECK(NULL != strstr(get_str(call_fetchdef(r, 0), "method"), "POST"), "exchange: the purchase is a POST");
598
+ }
599
+
600
+ static void t_exchange_explicit_refresh_wins(void) {
601
+ Rec* r = rec_new();
602
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), cmap(1, "refresh", v_str("REFRESH-OPT"))), v_map());
603
+ direct(sdk);
604
+ CHECK(NULL != strstr(call_body(r, 0), "REFRESH-OPT"), "exchange: exchange.refresh seats first and wins over the chain");
605
+ }
606
+
607
+ static void t_exchange_one_purchase_many_requests(void) {
608
+ Rec* r = rec_new();
609
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), v_map());
610
+ direct(sdk); direct(sdk); direct(sdk);
611
+ CHECK_INT_EQ(r->token, 1, "exchange: one purchase serves many requests");
612
+ CHECK_INT_EQ(r->api, 3, "exchange: all three went out");
613
+ }
614
+
615
+ static void t_exchange_401_rebuys_and_retries(void) {
616
+ Rec* r = rec_new();
617
+ r->script[0] = 401;
618
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), v_map());
619
+ voxgig_value* res = direct(sdk);
620
+ CHECK(res_ok(res), "401: the retried request succeeds");
621
+ CHECK_INT_EQ(r->api, 2, "401: the SAME request was tried again once");
622
+ CHECK_INT_EQ(r->token, 2, "401: a second token was bought");
623
+ // calls: 0 token, 1 api(401), 2 token, 3 api(200)
624
+ CHECK(credential_is(call_auth(r, 1, NULL), "ACCESS01"), "401: the first attempt carried the first token");
625
+ CHECK(credential_is(call_auth(r, 3, NULL), "ACCESS02"), "401: the retry carried the fresh token, rebuilt from auth.prefix");
626
+ }
627
+
628
+ static void t_exchange_retry_once_not_loop(void) {
629
+ Rec* r = rec_new();
630
+ r->script[0] = 401;
631
+ r->script[1] = 401;
632
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), v_map());
633
+ voxgig_value* res = direct(sdk);
634
+ CHECK(!res_ok(res), "retry budget: a second 401 on a fresh token is a real failure");
635
+ CHECK_INT_EQ(to_int(getp(res, "status")), 401, "retry budget: the API's own status is answered");
636
+ CHECK_INT_EQ(r->api, 2, "retry budget: two attempts, not a spin");
637
+ CHECK_INT_EQ(r->token, 2, "retry budget: two purchases, not a spin");
638
+ }
639
+
640
+ static void t_exchange_other_status_not_expiry(void) {
641
+ Rec* r = rec_new();
642
+ r->script[0] = 403;
643
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), v_map());
644
+ voxgig_value* res = direct(sdk);
645
+ CHECK(!res_ok(res), "403: not ok");
646
+ CHECK_INT_EQ(r->api, 1, "403: a status outside exchange.statuses is not an expiry - no retry");
647
+ CHECK_INT_EQ(r->token, 1, "403: no second purchase");
648
+ }
649
+
650
+ static void t_exchange_statuses_configurable(void) {
651
+ Rec* r = rec_new();
652
+ r->script[0] = 419;
653
+ ProjectNameSDK* sdk = live(r,
654
+ exchange_opts(refresh_store(), cmap(1, "statuses", clist(1, v_num(419)))), v_map());
655
+ CHECK(res_ok(direct(sdk)), "statuses: a configured status is an expiry");
656
+ CHECK_INT_EQ(r->api, 2, "statuses: retried once");
657
+ CHECK_INT_EQ(r->token, 2, "statuses: rebought once");
658
+ }
659
+
660
+ static void t_exchange_fields_configurable(void) {
661
+ Rec* r = rec_new();
662
+ r->tokenpath = "/oauth/exchange";
663
+ r->tokenfield = "token";
664
+ ProjectNameSDK* sdk = live(r,
665
+ exchange_opts(refresh_store(), cmap(3,
666
+ "path", v_str("/oauth/exchange"), "request", v_str("grant"), "response", v_str("token"))),
667
+ v_map());
668
+ CHECK(res_ok(direct(sdk)), "fields: the configured endpoint and fields work");
669
+ CHECK(NULL != strstr(get_str(voxgig_getelem(r->calls, v_int(0), voxgig_new_undef()), "url"), "http://api.test/oauth/exchange"),
670
+ "fields: the token endpoint is relative to base");
671
+ CHECK(NULL != strstr(call_body(r, 0), "\"grant\":\"REFRESH01\""), "fields: the request field name is configurable");
672
+ CHECK(credential_is(call_auth(r, 1, NULL), "ACCESS01"), "fields: the response field name is configurable");
673
+ }
674
+
675
+ static void t_exchange_explicit_apikey_spent_first(void) {
676
+ Rec* r = rec_new();
677
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), cmap(1, "apikey", v_str("HELD01")));
678
+ CHECK(res_ok(direct(sdk)), "held: the request succeeds");
679
+ CHECK_INT_EQ(r->token, 0, "held: an explicit apikey is spent before anything is bought");
680
+ CHECK(credential_is(call_auth(r, 0, NULL), "HELD01"), "held: the held access token went out");
681
+ }
682
+
683
+ static void t_exchange_expired_apikey_falls_through(void) {
684
+ Rec* r = rec_new();
685
+ r->script[0] = 401;
686
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), cmap(1, "apikey", v_str("STALE01")));
687
+ CHECK(res_ok(direct(sdk)), "expired: the retry succeeds");
688
+ CHECK_INT_EQ(r->token, 1, "expired: a stale held token falls through to ONE purchase");
689
+ CHECK(credential_is(call_auth(r, 0, NULL), "STALE01"), "expired: the stale token was spent first");
690
+ CHECK(credential_is(call_auth(r, 2, NULL), "ACCESS01"), "expired: the retry carried the bought token");
691
+ }
692
+
693
+ static void t_exchange_no_refresh_is_error(void) {
694
+ Rec* r = rec_new();
695
+ Custom* miss = custom_new(NULL, 0);
696
+ ProjectNameSDK* sdk = live(r, exchange_opts(clist(1, custom_spec(miss, NULL)), v_undef()), v_map());
697
+ voxgig_value* res = direct(sdk);
698
+ CHECK(!res_ok(res), "no refresh: the request fails");
699
+ CHECK(NULL != strstr(res_err(res), "no refresh token"), "no refresh: with the feature's own message");
700
+ CHECK_INT_EQ(r->api, 0, "no refresh: never an unauthenticated call");
701
+ CHECK_INT_EQ(r->token, 0, "no refresh: nothing bought");
702
+
703
+ Rec* r2 = rec_new();
704
+ ProjectNameSDK* ok = live(r2, exchange_opts(refresh_store(), v_undef()), v_map());
705
+ CHECK(res_ok(direct(ok)) && 1 == r2->api && 1 == r2->token, "control: a refresh token buys and sends once each");
706
+ }
707
+
708
+ static void t_exchange_failing_endpoint_surfaces_refusal(void) {
709
+ Rec* r = rec_new();
710
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), cmap(1, "apikey", v_str("STALE01")));
711
+ r->script[0] = 401;
712
+ r->tokenstatus = 500;
713
+ voxgig_value* res = direct(sdk);
714
+ CHECK(!res_ok(res), "endpoint down: the request fails");
715
+ CHECK_INT_EQ(to_int(getp(res, "status")), 401, "endpoint down: the API's refusal is answered, not the exchange error");
716
+ CHECK_INT_EQ(r->token, 1, "endpoint down: one attempted purchase, no spin");
717
+ CHECK_INT_EQ(r->api, 1, "endpoint down: no retry without a token");
718
+ }
719
+
720
+ static void t_exchange_auth_null_suppresses(void) {
721
+ Rec* r = rec_new();
722
+ r->script[0] = 401;
723
+ ProjectNameSDK* sdk = live(r, exchange_opts(refresh_store(), v_undef()), cmap(1, "auth", voxgig_new_null()));
724
+ voxgig_value* res = direct(sdk);
725
+ CHECK(!res_ok(res), "auth null + exchange: the refusal stands");
726
+ CHECK_INT_EQ(r->api, 1, "auth null + exchange: a refusal of a deliberately unauthenticated request is not retried");
727
+ bool present = true;
728
+ call_auth(r, r->token, &present); // the first API call follows any purchase
729
+ CHECK(!present, "auth null + exchange: no credential on the wire, refusal or not");
730
+ }
731
+
732
+ static void t_exchange_off_leaves_feature_as_before(void) {
733
+ Rec* r = rec_new();
734
+ ProjectNameSDK* sdk = live(r,
735
+ cmap(2, "providers", clist(1, memory(NULL, "APIKEY", "CHAIN01")), "exchange", cmap(1, "active", v_bool(false))),
736
+ v_map());
737
+ CHECK(res_ok(direct(sdk)), "exchange off: succeeds");
738
+ CHECK_INT_EQ(r->token, 0, "exchange off: nothing bought");
739
+ CHECK(credential_is(call_auth(r, 0, NULL), "CHAIN01"), "exchange off: the chain credential goes out as is");
740
+ }
741
+
742
+ static void t_exchange_test_mode_buys_nothing(void) {
743
+ // A TEST-mode client (fh_make builds one) with the exchange on: the
744
+ // feature answers a deterministic fake token and no token endpoint is
745
+ // ever reached. The harness recorder is the whole transport, so a
746
+ // purchase would be visible as a call it never gets.
747
+ voxgig_value* calls = NULL;
748
+ Fetcher* server = fh_recorder(NULL, NULL, &calls);
749
+ FhFeat feats[1] = {{feature_secrets_new(), exchange_opts(refresh_store(), v_undef())}};
750
+ FhHarness h = fh_make(server, feats, 1);
751
+ FhOpSpec o;
752
+ memset(&o, 0, sizeof(o));
753
+ FhOpResult res = fh_op(&h, o);
754
+ CHECK(res.ok, "test mode: the op succeeds");
755
+ CHECK_INT_EQ(rec_count(calls), 1, "test mode: one call, and it is the API call");
756
+ CHECK(credential_is(get_str(rec_headers(calls, 0), "authorization"), "test-access_token"),
757
+ "test mode: buys nothing and carries the deterministic fake token");
758
+ CHECK_INT_EQ(fh_track_int(feats[0].f, "buys"), 1, "test mode: counted as a (fake) purchase");
759
+ }
760
+
761
+ int main(void) {
762
+ RUN(t_inactive_apikey_as_before);
763
+ RUN(t_inactive_no_apikey_no_header);
764
+ RUN(t_apikey_wins_over_chain);
765
+ RUN(t_omitted_apikey_defers);
766
+ RUN(t_empty_apikey_defers);
767
+ RUN(t_auth_null_suppresses);
768
+ RUN(t_custom_provider_verbatim);
769
+ RUN(t_miss_everywhere_no_header);
770
+ RUN(t_provider_error_fails_direct);
771
+ RUN(t_provider_error_fails_graphql);
772
+ RUN(t_provider_error_fails_entity_op);
773
+ RUN(t_recovers_after_transient_failure);
774
+ RUN(t_cache_false_asks_every_time);
775
+ RUN(t_cache_false_miss_retracts);
776
+ RUN(t_secret_name_configurable);
777
+ RUN(t_sekreto_is_live);
778
+ RUN(t_unknown_kind_refused);
779
+ RUN(t_malformed_entry_never_dropped);
780
+ RUN(t_selected_kinds_in_vocabulary);
781
+ RUN(t_raw_paths_get_chain_credential);
782
+ RUN(t_exchange_buys_and_carries);
783
+ RUN(t_exchange_explicit_refresh_wins);
784
+ RUN(t_exchange_one_purchase_many_requests);
785
+ RUN(t_exchange_401_rebuys_and_retries);
786
+ RUN(t_exchange_retry_once_not_loop);
787
+ RUN(t_exchange_other_status_not_expiry);
788
+ RUN(t_exchange_statuses_configurable);
789
+ RUN(t_exchange_fields_configurable);
790
+ RUN(t_exchange_explicit_apikey_spent_first);
791
+ RUN(t_exchange_expired_apikey_falls_through);
792
+ RUN(t_exchange_no_refresh_is_error);
793
+ RUN(t_exchange_failing_endpoint_surfaces_refusal);
794
+ RUN(t_exchange_auth_null_suppresses);
795
+ RUN(t_exchange_off_leaves_feature_as_before);
796
+ RUN(t_exchange_test_mode_buys_nothing);
797
+
798
+ // The generator's runtime lane reads this line: a suite that RAN fewer
799
+ // cases than it declares was trimmed, not passed.
800
+ printf("secrets: ran %d case(s)\n", CASES);
801
+ TEST_SUMMARY("secrets");
802
+ }