@stackonward/cli 0.0.1 → 1.0.1

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 (638) hide show
  1. package/dist/index.js +4588 -205
  2. package/dist/index.js.map +1 -1
  3. package/dist/resources/apple/.claude/docs/architecture-template.md +54 -0
  4. package/dist/resources/apple/.claude/docs/architecture.md +52 -0
  5. package/dist/resources/apple/.claude/docs/index.md +13 -0
  6. package/dist/resources/apple/.claude/docs/security-lifecycle.md +57 -0
  7. package/dist/resources/apple/.claude/docs/stack.md +93 -0
  8. package/dist/resources/apple/.claude/docs/verification.md +63 -0
  9. package/dist/resources/apple/AGENTS.md +397 -0
  10. package/dist/resources/apple/CLAUDE.md +397 -0
  11. package/dist/resources/apple/Gemfile +2 -0
  12. package/dist/resources/apple/Gemfile.lock +365 -0
  13. package/dist/resources/apple/_gitignore +14 -0
  14. package/dist/resources/apple/fastlane/Fastfile +123 -0
  15. package/dist/resources/components/nuxt/site-source/app/app.vue +42 -0
  16. package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +1035 -0
  17. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/.gitkeep +0 -0
  18. package/dist/resources/components/nuxt/site-source/app/assets/icons/common/.gitkeep +0 -0
  19. package/dist/resources/components/nuxt/site-source/app/components/site/ApplicationHeader.vue +35 -0
  20. package/dist/resources/components/nuxt/site-source/app/components/site/ApplicationShell.vue +56 -0
  21. package/dist/resources/components/nuxt/site-source/app/components/site/ApplicationSidebar.vue +40 -0
  22. package/dist/resources/components/nuxt/site-source/app/components/site/BackToTop.vue +42 -0
  23. package/dist/resources/components/nuxt/site-source/app/components/site/ContentArticlePage.vue +40 -0
  24. package/dist/resources/components/nuxt/site-source/app/components/site/ContentListPage.vue +30 -0
  25. package/dist/resources/components/nuxt/site-source/app/components/site/CookieConsent.vue +18 -0
  26. package/dist/resources/components/nuxt/site-source/app/components/site/MarketingFooter.vue +52 -0
  27. package/dist/resources/components/nuxt/site-source/app/components/site/MarketingHeader.vue +106 -0
  28. package/dist/resources/components/nuxt/site-source/app/components/site/MarketingShell.vue +35 -0
  29. package/dist/resources/components/nuxt/site-source/app/components/site/SiteBrand.vue +10 -0
  30. package/dist/resources/components/nuxt/site-source/app/components/site/StaticPage.vue +21 -0
  31. package/dist/resources/components/nuxt/site-source/app/components/site/ToastViewport.vue +25 -0
  32. package/dist/resources/components/nuxt/site-source/app/components/ui/BaseButton.vue +86 -0
  33. package/dist/resources/components/nuxt/site-source/app/components/ui/BaseGradientBorder.vue +59 -0
  34. package/dist/resources/components/nuxt/site-source/app/components/ui/BaseModal.vue +131 -0
  35. package/dist/resources/components/nuxt/site-source/app/components/ui/BasePopover.vue +150 -0
  36. package/dist/resources/components/nuxt/site-source/app/components/ui/BaseText.vue +138 -0
  37. package/dist/resources/components/nuxt/site-source/app/components/ui/CheckoutStatus.vue +112 -0
  38. package/dist/resources/components/nuxt/site-source/app/components/ui/CheckoutSummary.vue +172 -0
  39. package/dist/resources/components/nuxt/site-source/app/components/ui/GoogleSignInButton.vue +113 -0
  40. package/dist/resources/components/nuxt/site-source/app/components/ui/InfiniteScroll.vue +123 -0
  41. package/dist/resources/components/nuxt/site-source/app/components/ui/YoutubeEmbed.vue +126 -0
  42. package/dist/resources/components/nuxt/site-source/app/composables/useAnalytics.ts +24 -0
  43. package/dist/resources/components/nuxt/site-source/app/composables/useCheckout.ts +121 -0
  44. package/dist/resources/components/nuxt/site-source/app/composables/useConsent.ts +31 -0
  45. package/dist/resources/components/nuxt/site-source/app/composables/useGoogleIdentity.ts +163 -0
  46. package/dist/resources/components/nuxt/site-source/app/composables/useNavigation.ts +11 -0
  47. package/dist/resources/components/nuxt/site-source/app/composables/useSeo.ts +33 -0
  48. package/dist/resources/components/nuxt/site-source/app/composables/useSiteShell.ts +58 -0
  49. package/dist/resources/components/nuxt/site-source/app/composables/useToasts.ts +48 -0
  50. package/dist/resources/components/nuxt/site-source/app/layouts/app.vue +5 -0
  51. package/dist/resources/components/nuxt/site-source/app/layouts/default.vue +5 -0
  52. package/dist/resources/components/nuxt/site-source/app/plugins/analytics.client.ts +121 -0
  53. package/dist/resources/components/nuxt/site-source/app/plugins/google-identity.client.ts +24 -0
  54. package/dist/resources/components/nuxt/site-source/app/theme/accordion.ts +9 -0
  55. package/dist/resources/components/nuxt/site-source/app/theme/breadcrumb.ts +8 -0
  56. package/dist/resources/components/nuxt/site-source/app/theme/button.ts +92 -0
  57. package/dist/resources/components/nuxt/site-source/app/theme/carousel.ts +11 -0
  58. package/dist/resources/components/nuxt/site-source/app/theme/checkbox.ts +12 -0
  59. package/dist/resources/components/nuxt/site-source/app/theme/index.ts +30 -0
  60. package/dist/resources/components/nuxt/site-source/app/theme/input.ts +47 -0
  61. package/dist/resources/components/nuxt/site-source/app/theme/modal.ts +9 -0
  62. package/dist/resources/components/nuxt/site-source/app/theme/pagination.ts +6 -0
  63. package/dist/resources/components/nuxt/site-source/app/theme/select.ts +23 -0
  64. package/dist/resources/components/nuxt/site-source/app/theme/stepper.ts +10 -0
  65. package/dist/resources/components/nuxt/site-source/app/theme/toast.ts +8 -0
  66. package/dist/resources/components/nuxt/site-source/app/theme/tooltip.ts +6 -0
  67. package/dist/resources/components/nuxt/site-source/app/types/site-shell.ts +85 -0
  68. package/dist/resources/components/react/ui/Button.tsx +73 -0
  69. package/dist/resources/components/react/ui/Input.tsx +44 -0
  70. package/dist/resources/components/registry.json +355 -0
  71. package/dist/resources/components/vue/feedback/Toast.vue +120 -0
  72. package/dist/resources/components/vue/feedback/useToast.ts +70 -0
  73. package/dist/resources/components/vue/media/FileUpload.vue +229 -0
  74. package/dist/resources/components/vue/media/VideoPlayer.vue +157 -0
  75. package/dist/resources/components/vue/ui/AppImage.vue +147 -0
  76. package/dist/resources/components/vue/ui/BottomSheet.vue +217 -0
  77. package/dist/resources/components/vue/ui/Button.vue +154 -0
  78. package/dist/resources/components/vue/ui/Card.vue +111 -0
  79. package/dist/resources/components/vue/ui/Input.vue +113 -0
  80. package/dist/resources/components/vue/ui/Modal.vue +193 -0
  81. package/dist/resources/components/vue/ui/ScrollArea.vue +163 -0
  82. package/dist/resources/components/vue/ui/Tooltip.vue +115 -0
  83. package/dist/resources/registry.json +158 -0
  84. package/dist/resources/snippets/node/cache.ts +116 -0
  85. package/dist/resources/snippets/node/date.ts +126 -0
  86. package/dist/resources/snippets/node/logger.ts +104 -0
  87. package/dist/resources/snippets/node/validation.ts +132 -0
  88. package/dist/resources/snippets/react/useDebounce.ts +90 -0
  89. package/dist/resources/snippets/registry.json +65 -0
  90. package/dist/resources/snippets/vue/useAsync.ts +96 -0
  91. package/dist/resources/snippets/vue/usePagination.ts +102 -0
  92. package/dist/resources/templates/base/.claude/docs/architecture-template.md +459 -0
  93. package/dist/resources/templates/base/.claude/docs/architecture.md +137 -0
  94. package/dist/resources/templates/base/.claude/docs/index.md +42 -0
  95. package/dist/resources/templates/base/.claude/docs/security-lifecycle.md +129 -0
  96. package/dist/resources/templates/base/.claude/docs/verification.md +121 -0
  97. package/dist/resources/templates/base/AGENTS.md +415 -0
  98. package/dist/resources/templates/base/CLAUDE.md +415 -0
  99. package/dist/resources/templates/go-backend/.claude/docs/api-design.md +486 -0
  100. package/dist/resources/templates/go-backend/.claude/docs/architecture-template.md +449 -0
  101. package/dist/resources/templates/go-backend/.claude/docs/check-list.md +155 -0
  102. package/dist/resources/templates/go-backend/.claude/docs/database.md +369 -0
  103. package/dist/resources/templates/go-backend/.claude/docs/e2e-standards.md +161 -0
  104. package/dist/resources/templates/go-backend/.claude/docs/error-handling.md +558 -0
  105. package/dist/resources/templates/go-backend/.claude/docs/event-runtime-boundary.md +124 -0
  106. package/dist/resources/templates/go-backend/.claude/docs/identifier-contract.md +195 -0
  107. package/dist/resources/templates/go-backend/.claude/docs/index.md +22 -0
  108. package/dist/resources/templates/go-backend/.claude/docs/logging.md +314 -0
  109. package/dist/resources/templates/go-backend/.claude/docs/mediagen-provider-onboarding-sop.md +917 -0
  110. package/dist/resources/templates/go-backend/.claude/docs/mediagen-provider-quick-reference.md +195 -0
  111. package/dist/resources/templates/go-backend/.claude/docs/module-standards.md +309 -0
  112. package/dist/resources/templates/go-backend/.claude/docs/openapi-contract.md +141 -0
  113. package/dist/resources/templates/go-backend/.claude/docs/payment-gateway-onboarding.md +283 -0
  114. package/dist/resources/templates/go-backend/.claude/docs/platform-boundary.md +191 -0
  115. package/dist/resources/templates/go-backend/.claude/docs/quality-gates.md +112 -0
  116. package/dist/resources/templates/go-backend/.dockerignore +6 -0
  117. package/dist/resources/templates/go-backend/.env.example +13 -0
  118. package/dist/resources/templates/go-backend/.gitattributes +2 -0
  119. package/dist/resources/templates/go-backend/AGENTS.md +1139 -0
  120. package/dist/resources/templates/go-backend/CLAUDE.md +1139 -0
  121. package/dist/resources/templates/go-backend/Dockerfile +14 -0
  122. package/dist/resources/templates/go-backend/Makefile +71 -0
  123. package/dist/resources/templates/go-backend/README.md +66 -0
  124. package/dist/resources/templates/go-backend/_gitignore +10 -0
  125. package/dist/resources/templates/go-backend/api/openapi/README.md +29 -0
  126. package/dist/resources/templates/go-backend/api/openapi/dist/artifact-manifest.json +19 -0
  127. package/dist/resources/templates/go-backend/api/openapi/dist/conformance-matrix.json +276 -0
  128. package/dist/resources/templates/go-backend/api/openapi/document.go +30 -0
  129. package/dist/resources/templates/go-backend/api/openapi/package.json +28 -0
  130. package/dist/resources/templates/go-backend/api/openapi/pnpm-lock.yaml +3052 -0
  131. package/dist/resources/templates/go-backend/api/openapi/redocly-source.yaml +18 -0
  132. package/dist/resources/templates/go-backend/api/openapi/redocly.yaml +8 -0
  133. package/dist/resources/templates/go-backend/api/openapi/sdk-generators.json +16 -0
  134. package/dist/resources/templates/go-backend/api/openapi/service-registry.json +39 -0
  135. package/dist/resources/templates/go-backend/api/openapi/services/product-api/conformance/runtime.arazzo.yaml +65 -0
  136. package/dist/resources/templates/go-backend/api/openapi/services/product-api/dist/internal.openapi.json +327 -0
  137. package/dist/resources/templates/go-backend/api/openapi/services/product-api/dist/runtime-contract.json +56 -0
  138. package/dist/resources/templates/go-backend/api/openapi/services/product-api/openapi.yaml +222 -0
  139. package/dist/resources/templates/go-backend/api/openapi/services/worker/conformance/runtime.arazzo.yaml +65 -0
  140. package/dist/resources/templates/go-backend/api/openapi/services/worker/dist/internal.openapi.json +327 -0
  141. package/dist/resources/templates/go-backend/api/openapi/services/worker/dist/runtime-contract.json +56 -0
  142. package/dist/resources/templates/go-backend/api/openapi/services/worker/openapi.yaml +222 -0
  143. package/dist/resources/templates/go-backend/api/openapi/tooling/breaking.mjs +416 -0
  144. package/dist/resources/templates/go-backend/api/openapi/tooling/build.mjs +103 -0
  145. package/dist/resources/templates/go-backend/api/openapi/tooling/conformance_coverage.mjs +111 -0
  146. package/dist/resources/templates/go-backend/api/openapi/tooling/contract_compiler.mjs +62 -0
  147. package/dist/resources/templates/go-backend/api/openapi/tooling/contract_policy.mjs +208 -0
  148. package/dist/resources/templates/go-backend/api/openapi/tooling/delegated_contract.mjs +302 -0
  149. package/dist/resources/templates/go-backend/api/openapi/tooling/deterministic_archive.mjs +69 -0
  150. package/dist/resources/templates/go-backend/api/openapi/tooling/documentation_example_privacy.mjs +93 -0
  151. package/dist/resources/templates/go-backend/api/openapi/tooling/documentation_projection.mjs +498 -0
  152. package/dist/resources/templates/go-backend/api/openapi/tooling/documentation_quality.mjs +346 -0
  153. package/dist/resources/templates/go-backend/api/openapi/tooling/git_artifact_history.mjs +188 -0
  154. package/dist/resources/templates/go-backend/api/openapi/tooling/manifest.mjs +170 -0
  155. package/dist/resources/templates/go-backend/api/openapi/tooling/overlay.mjs +98 -0
  156. package/dist/resources/templates/go-backend/api/openapi/tooling/project.mjs +549 -0
  157. package/dist/resources/templates/go-backend/api/openapi/tooling/route_contract.mjs +156 -0
  158. package/dist/resources/templates/go-backend/api/openapi/tooling/runtime_conformance.mjs +193 -0
  159. package/dist/resources/templates/go-backend/api/openapi/tooling/scalar_asset_localization.mjs +164 -0
  160. package/dist/resources/templates/go-backend/api/openapi/tooling/sdk_generator.mjs +229 -0
  161. package/dist/resources/templates/go-backend/api/openapi/tooling/semantic_version.mjs +64 -0
  162. package/dist/resources/templates/go-backend/api/openapi/tooling/service_contract_inputs.mjs +16 -0
  163. package/dist/resources/templates/go-backend/api/openapi/tooling/test/breaking.test.mjs +365 -0
  164. package/dist/resources/templates/go-backend/api/openapi/tooling/test/build.test.mjs +42 -0
  165. package/dist/resources/templates/go-backend/api/openapi/tooling/test/conformance_coverage.test.mjs +88 -0
  166. package/dist/resources/templates/go-backend/api/openapi/tooling/test/contract_policy.test.mjs +257 -0
  167. package/dist/resources/templates/go-backend/api/openapi/tooling/test/delegated_contract.test.mjs +205 -0
  168. package/dist/resources/templates/go-backend/api/openapi/tooling/test/documentation_example_privacy.test.mjs +51 -0
  169. package/dist/resources/templates/go-backend/api/openapi/tooling/test/documentation_projection.test.mjs +368 -0
  170. package/dist/resources/templates/go-backend/api/openapi/tooling/test/documentation_quality.test.mjs +151 -0
  171. package/dist/resources/templates/go-backend/api/openapi/tooling/test/git_artifact_history.test.mjs +60 -0
  172. package/dist/resources/templates/go-backend/api/openapi/tooling/test/head_policy.test.mjs +8 -0
  173. package/dist/resources/templates/go-backend/api/openapi/tooling/test/overlay.test.mjs +44 -0
  174. package/dist/resources/templates/go-backend/api/openapi/tooling/test/projection.test.mjs +270 -0
  175. package/dist/resources/templates/go-backend/api/openapi/tooling/test/redocly_documentation_rules.test.mjs +146 -0
  176. package/dist/resources/templates/go-backend/api/openapi/tooling/test/release_manifest.test.mjs +76 -0
  177. package/dist/resources/templates/go-backend/api/openapi/tooling/test/route_contract.test.mjs +119 -0
  178. package/dist/resources/templates/go-backend/api/openapi/tooling/test/runtime_conformance.test.mjs +75 -0
  179. package/dist/resources/templates/go-backend/api/openapi/tooling/test/scalar_asset_localization.test.mjs +50 -0
  180. package/dist/resources/templates/go-backend/api/openapi/tooling/test/sdk_generator.test.mjs +31 -0
  181. package/dist/resources/templates/go-backend/api/openapi/tooling/test/semantic_version.test.mjs +60 -0
  182. package/dist/resources/templates/go-backend/api/openapi/tooling/test/service_contract_inputs.test.mjs +27 -0
  183. package/dist/resources/templates/go-backend/cmd/internal/runtime/resources.go +125 -0
  184. package/dist/resources/templates/go-backend/cmd/internal/runtime/runtime.go +102 -0
  185. package/dist/resources/templates/go-backend/cmd/internal/runtime/runtime_test.go +70 -0
  186. package/dist/resources/templates/go-backend/cmd/product-api/lifecycle_test.go +58 -0
  187. package/dist/resources/templates/go-backend/cmd/product-api/main.go +32 -0
  188. package/dist/resources/templates/go-backend/cmd/product-api/wire.go +13 -0
  189. package/dist/resources/templates/go-backend/cmd/product-api/wire_gen.go +94 -0
  190. package/dist/resources/templates/go-backend/cmd/worker/main.go +47 -0
  191. package/dist/resources/templates/go-backend/cmd/worker/wire.go +14 -0
  192. package/dist/resources/templates/go-backend/cmd/worker/wire_gen.go +111 -0
  193. package/dist/resources/templates/go-backend/configs/config.yaml +37 -0
  194. package/dist/resources/templates/go-backend/deployments/compose.yaml +71 -0
  195. package/dist/resources/templates/go-backend/docs/openapi-provenance.json +489 -0
  196. package/dist/resources/templates/go-backend/docs/runtime-provenance.json +175 -0
  197. package/dist/resources/templates/go-backend/docs/source-provenance.json +3568 -0
  198. package/dist/resources/templates/go-backend/docs/standards-provenance.json +1624 -0
  199. package/dist/resources/templates/go-backend/go.mod +256 -0
  200. package/dist/resources/templates/go-backend/go.sum +805 -0
  201. package/dist/resources/templates/go-backend/internal/common/canonicaljson/canonical.go +370 -0
  202. package/dist/resources/templates/go-backend/internal/common/canonicaljson/canonical_test.go +223 -0
  203. package/dist/resources/templates/go-backend/internal/common/checkoutbff/contracts.go +125 -0
  204. package/dist/resources/templates/go-backend/internal/common/checkoutbff/platform_client.go +212 -0
  205. package/dist/resources/templates/go-backend/internal/common/checkoutbff/platform_client_test.go +319 -0
  206. package/dist/resources/templates/go-backend/internal/common/contentaccess/contentaccess.go +50 -0
  207. package/dist/resources/templates/go-backend/internal/common/contracts/contact.go +85 -0
  208. package/dist/resources/templates/go-backend/internal/common/ctxutil/identity.go +20 -0
  209. package/dist/resources/templates/go-backend/internal/common/ctxutil/product.go +53 -0
  210. package/dist/resources/templates/go-backend/internal/common/enumdomain/payment.go +12 -0
  211. package/dist/resources/templates/go-backend/internal/common/enums/dto_admin.go +9 -0
  212. package/dist/resources/templates/go-backend/internal/common/enums/enum.go +1247 -0
  213. package/dist/resources/templates/go-backend/internal/common/enums/handler_admin.go +66 -0
  214. package/dist/resources/templates/go-backend/internal/common/enums/module.go +46 -0
  215. package/dist/resources/templates/go-backend/internal/common/enums/provider.go +10 -0
  216. package/dist/resources/templates/go-backend/internal/common/enums/register.go +11 -0
  217. package/dist/resources/templates/go-backend/internal/common/enums/register_audit.go +274 -0
  218. package/dist/resources/templates/go-backend/internal/common/enums/register_command_replay.go +12 -0
  219. package/dist/resources/templates/go-backend/internal/common/enums/register_config.go +27 -0
  220. package/dist/resources/templates/go-backend/internal/common/enums/register_locale.go +37 -0
  221. package/dist/resources/templates/go-backend/internal/common/enums/register_ops.go +14 -0
  222. package/dist/resources/templates/go-backend/internal/common/enums/register_outbox.go +15 -0
  223. package/dist/resources/templates/go-backend/internal/common/enums/register_rule_expression.go +63 -0
  224. package/dist/resources/templates/go-backend/internal/common/enums/registry.go +77 -0
  225. package/dist/resources/templates/go-backend/internal/common/httporigin/origin.go +62 -0
  226. package/dist/resources/templates/go-backend/internal/common/httporigin/origin_test.go +38 -0
  227. package/dist/resources/templates/go-backend/internal/common/iamcredential/plaintext.go +35 -0
  228. package/dist/resources/templates/go-backend/internal/common/iamcredential/plaintext_test.go +29 -0
  229. package/dist/resources/templates/go-backend/internal/common/iamscope/scope.go +212 -0
  230. package/dist/resources/templates/go-backend/internal/common/iamscope/scope_binding.go +26 -0
  231. package/dist/resources/templates/go-backend/internal/common/iamscope/scope_filter.go +173 -0
  232. package/dist/resources/templates/go-backend/internal/common/iamscope/scope_filter_test.go +279 -0
  233. package/dist/resources/templates/go-backend/internal/common/internalapi/authorization_details.go +10 -0
  234. package/dist/resources/templates/go-backend/internal/common/internalapi/route_permission_policies.go +660 -0
  235. package/dist/resources/templates/go-backend/internal/common/internalapi/route_permission_policies_test.go +331 -0
  236. package/dist/resources/templates/go-backend/internal/common/internalservice/audience_token_source.go +166 -0
  237. package/dist/resources/templates/go-backend/internal/common/internalservice/bootstrap_token_exchanger.go +183 -0
  238. package/dist/resources/templates/go-backend/internal/common/internalservice/bound_service_token_signer.go +52 -0
  239. package/dist/resources/templates/go-backend/internal/common/internalservice/delegated_access_token_exchanger.go +189 -0
  240. package/dist/resources/templates/go-backend/internal/common/internalservice/delegated_access_token_exchanger_test.go +159 -0
  241. package/dist/resources/templates/go-backend/internal/common/internalservice/product_scoped_service_token_signer.go +92 -0
  242. package/dist/resources/templates/go-backend/internal/common/internalservice/provider.go +25 -0
  243. package/dist/resources/templates/go-backend/internal/common/internalservice/request_signer.go +86 -0
  244. package/dist/resources/templates/go-backend/internal/common/internalservice/resolver.go +69 -0
  245. package/dist/resources/templates/go-backend/internal/common/internalservice/resolver_test.go +42 -0
  246. package/dist/resources/templates/go-backend/internal/common/internalservice/route_scope.go +95 -0
  247. package/dist/resources/templates/go-backend/internal/common/internalservice/route_scope_test.go +78 -0
  248. package/dist/resources/templates/go-backend/internal/common/internalservice/service_identity_test.go +387 -0
  249. package/dist/resources/templates/go-backend/internal/common/jsonschema/compiler.go +100 -0
  250. package/dist/resources/templates/go-backend/internal/common/jsonschema/compiler_test.go +41 -0
  251. package/dist/resources/templates/go-backend/internal/common/jwksapi/handler.go +83 -0
  252. package/dist/resources/templates/go-backend/internal/common/jwksapi/handler_test.go +104 -0
  253. package/dist/resources/templates/go-backend/internal/common/locale/locale.go +145 -0
  254. package/dist/resources/templates/go-backend/internal/common/locale/locale_test.go +75 -0
  255. package/dist/resources/templates/go-backend/internal/common/mediaref/mediaref.go +54 -0
  256. package/dist/resources/templates/go-backend/internal/common/mediaref/mediaref_test.go +77 -0
  257. package/dist/resources/templates/go-backend/internal/common/middleware/apikey_auth.go +320 -0
  258. package/dist/resources/templates/go-backend/internal/common/middleware/apikey_throttle.go +158 -0
  259. package/dist/resources/templates/go-backend/internal/common/middleware/auth.go +40 -0
  260. package/dist/resources/templates/go-backend/internal/common/middleware/auth_test.go +50 -0
  261. package/dist/resources/templates/go-backend/internal/common/middleware/client_context.go +86 -0
  262. package/dist/resources/templates/go-backend/internal/common/middleware/cors.go +68 -0
  263. package/dist/resources/templates/go-backend/internal/common/middleware/event_context.go +18 -0
  264. package/dist/resources/templates/go-backend/internal/common/middleware/gateway_identity.go +279 -0
  265. package/dist/resources/templates/go-backend/internal/common/middleware/helpers.go +159 -0
  266. package/dist/resources/templates/go-backend/internal/common/middleware/iam_delegated_route_permission.go +171 -0
  267. package/dist/resources/templates/go-backend/internal/common/middleware/iam_delegated_token_auth.go +151 -0
  268. package/dist/resources/templates/go-backend/internal/common/middleware/iam_delegated_token_auth_test.go +99 -0
  269. package/dist/resources/templates/go-backend/internal/common/middleware/iam_service_identity.go +138 -0
  270. package/dist/resources/templates/go-backend/internal/common/middleware/iam_service_token_auth.go +74 -0
  271. package/dist/resources/templates/go-backend/internal/common/middleware/idempotent.go +273 -0
  272. package/dist/resources/templates/go-backend/internal/common/middleware/idempotent_test.go +83 -0
  273. package/dist/resources/templates/go-backend/internal/common/middleware/internal_route_permission.go +45 -0
  274. package/dist/resources/templates/go-backend/internal/common/middleware/internal_route_permission_test.go +186 -0
  275. package/dist/resources/templates/go-backend/internal/common/middleware/logging.go +191 -0
  276. package/dist/resources/templates/go-backend/internal/common/middleware/metrics.go +45 -0
  277. package/dist/resources/templates/go-backend/internal/common/middleware/middleware_test.go +1516 -0
  278. package/dist/resources/templates/go-backend/internal/common/middleware/principal_context.go +160 -0
  279. package/dist/resources/templates/go-backend/internal/common/middleware/principal_context_test.go +355 -0
  280. package/dist/resources/templates/go-backend/internal/common/middleware/product_context.go +100 -0
  281. package/dist/resources/templates/go-backend/internal/common/middleware/recovery.go +40 -0
  282. package/dist/resources/templates/go-backend/internal/common/middleware/referral_attribution.go +178 -0
  283. package/dist/resources/templates/go-backend/internal/common/middleware/requestid.go +48 -0
  284. package/dist/resources/templates/go-backend/internal/common/middleware/resource_permission.go +790 -0
  285. package/dist/resources/templates/go-backend/internal/common/middleware/resource_permission_test.go +440 -0
  286. package/dist/resources/templates/go-backend/internal/common/middleware/service_credential_auth.go +67 -0
  287. package/dist/resources/templates/go-backend/internal/common/middleware/tenant.go +148 -0
  288. package/dist/resources/templates/go-backend/internal/common/middleware/throttle.go +310 -0
  289. package/dist/resources/templates/go-backend/internal/common/middleware/trusted_client_ip.go +23 -0
  290. package/dist/resources/templates/go-backend/internal/common/middleware/trusted_client_ip_test.go +60 -0
  291. package/dist/resources/templates/go-backend/internal/common/money/currency.go +25 -0
  292. package/dist/resources/templates/go-backend/internal/common/money/errors.go +23 -0
  293. package/dist/resources/templates/go-backend/internal/common/money/format.go +123 -0
  294. package/dist/resources/templates/go-backend/internal/common/money/format_test.go +204 -0
  295. package/dist/resources/templates/go-backend/internal/common/money/gateway.go +144 -0
  296. package/dist/resources/templates/go-backend/internal/common/money/gateway_test.go +165 -0
  297. package/dist/resources/templates/go-backend/internal/common/money/rounding.go +56 -0
  298. package/dist/resources/templates/go-backend/internal/common/money/rounding_test.go +51 -0
  299. package/dist/resources/templates/go-backend/internal/common/principal/context.go +56 -0
  300. package/dist/resources/templates/go-backend/internal/common/principal/principal.go +49 -0
  301. package/dist/resources/templates/go-backend/internal/common/productcode/code.go +24 -0
  302. package/dist/resources/templates/go-backend/internal/common/productcode/code_test.go +35 -0
  303. package/dist/resources/templates/go-backend/internal/common/providerconfig/schema.go +577 -0
  304. package/dist/resources/templates/go-backend/internal/common/providerconfig/schema_test.go +47 -0
  305. package/dist/resources/templates/go-backend/internal/common/requestprivacy/request.go +246 -0
  306. package/dist/resources/templates/go-backend/internal/common/requestprivacy/request_test.go +170 -0
  307. package/dist/resources/templates/go-backend/internal/common/response/pagination.go +60 -0
  308. package/dist/resources/templates/go-backend/internal/common/response/response.go +110 -0
  309. package/dist/resources/templates/go-backend/internal/common/response/response_test.go +463 -0
  310. package/dist/resources/templates/go-backend/internal/common/ruleexpression/ast.go +141 -0
  311. package/dist/resources/templates/go-backend/internal/common/ruleexpression/ast_test.go +81 -0
  312. package/dist/resources/templates/go-backend/internal/common/ruleexpression/canonical.go +184 -0
  313. package/dist/resources/templates/go-backend/internal/common/ruleexpression/canonical_test.go +319 -0
  314. package/dist/resources/templates/go-backend/internal/common/ruleexpression/decoder.go +555 -0
  315. package/dist/resources/templates/go-backend/internal/common/ruleexpression/decoder_test.go +260 -0
  316. package/dist/resources/templates/go-backend/internal/common/ruleexpression/errors.go +55 -0
  317. package/dist/resources/templates/go-backend/internal/common/ruleexpression/evaluator.go +96 -0
  318. package/dist/resources/templates/go-backend/internal/common/ruleexpression/evaluator_test.go +220 -0
  319. package/dist/resources/templates/go-backend/internal/common/ruleexpression/fuzz_test.go +116 -0
  320. package/dist/resources/templates/go-backend/internal/common/ruleexpression/limits.go +331 -0
  321. package/dist/resources/templates/go-backend/internal/common/ruleexpression/schema.go +412 -0
  322. package/dist/resources/templates/go-backend/internal/common/ruleexpression/schema_test.go +164 -0
  323. package/dist/resources/templates/go-backend/internal/common/runtimeinput/runtime_input.go +627 -0
  324. package/dist/resources/templates/go-backend/internal/common/runtimeinput/runtime_input_test.go +245 -0
  325. package/dist/resources/templates/go-backend/internal/common/semver/errors.go +6 -0
  326. package/dist/resources/templates/go-backend/internal/common/semver/semver.go +125 -0
  327. package/dist/resources/templates/go-backend/internal/common/semver/semver_test.go +205 -0
  328. package/dist/resources/templates/go-backend/internal/common/serviceidentity/audience.go +48 -0
  329. package/dist/resources/templates/go-backend/internal/common/serviceidentity/audience_test.go +35 -0
  330. package/dist/resources/templates/go-backend/internal/common/serviceidentity/resource_identifier.go +33 -0
  331. package/dist/resources/templates/go-backend/internal/common/serviceidentity/resource_identifier_test.go +32 -0
  332. package/dist/resources/templates/go-backend/internal/common/tradeno/generator.go +99 -0
  333. package/dist/resources/templates/go-backend/internal/common/tradeno/generator_test.go +396 -0
  334. package/dist/resources/templates/go-backend/internal/common/tradeno/prefix.go +247 -0
  335. package/dist/resources/templates/go-backend/internal/common/types/asset_url.go +11 -0
  336. package/dist/resources/templates/go-backend/internal/common/types/billing.go +89 -0
  337. package/dist/resources/templates/go-backend/internal/common/types/billing_test.go +77 -0
  338. package/dist/resources/templates/go-backend/internal/common/types/country.go +74 -0
  339. package/dist/resources/templates/go-backend/internal/common/types/country_test.go +28 -0
  340. package/dist/resources/templates/go-backend/internal/common/types/currency.go +60 -0
  341. package/dist/resources/templates/go-backend/internal/common/types/identifiers.go +41 -0
  342. package/dist/resources/templates/go-backend/internal/common/types/operator.go +28 -0
  343. package/dist/resources/templates/go-backend/internal/common/types/subscription_entitlement.go +24 -0
  344. package/dist/resources/templates/go-backend/internal/common/types/subscription_entitlement_test.go +35 -0
  345. package/dist/resources/templates/go-backend/internal/common/types/tier.go +72 -0
  346. package/dist/resources/templates/go-backend/internal/common/types/types.go +411 -0
  347. package/dist/resources/templates/go-backend/internal/common/types/types_test.go +295 -0
  348. package/dist/resources/templates/go-backend/internal/common/utils/constants.go +100 -0
  349. package/dist/resources/templates/go-backend/internal/common/utils/crypto.go +158 -0
  350. package/dist/resources/templates/go-backend/internal/common/utils/crypto_test.go +345 -0
  351. package/dist/resources/templates/go-backend/internal/common/utils/duration.go +25 -0
  352. package/dist/resources/templates/go-backend/internal/common/utils/helpers.go +18 -0
  353. package/dist/resources/templates/go-backend/internal/common/utils/json.go +62 -0
  354. package/dist/resources/templates/go-backend/internal/common/utils/json_test.go +76 -0
  355. package/dist/resources/templates/go-backend/internal/common/utils/pagination.go +51 -0
  356. package/dist/resources/templates/go-backend/internal/common/utils/pagination_test.go +45 -0
  357. package/dist/resources/templates/go-backend/internal/common/utils/pointer.go +107 -0
  358. package/dist/resources/templates/go-backend/internal/common/utils/pointer_test.go +15 -0
  359. package/dist/resources/templates/go-backend/internal/common/utils/slice.go +24 -0
  360. package/dist/resources/templates/go-backend/internal/common/utils/snowflake.go +110 -0
  361. package/dist/resources/templates/go-backend/internal/common/utils/snowflake_test.go +316 -0
  362. package/dist/resources/templates/go-backend/internal/common/utils/string.go +16 -0
  363. package/dist/resources/templates/go-backend/internal/common/utils/timefmt.go +42 -0
  364. package/dist/resources/templates/go-backend/internal/common/validator/validator.go +136 -0
  365. package/dist/resources/templates/go-backend/internal/common/validator/validator_test.go +240 -0
  366. package/dist/resources/templates/go-backend/internal/common/youtubeapi/client.go +224 -0
  367. package/dist/resources/templates/go-backend/internal/core/apicontract/document.go +269 -0
  368. package/dist/resources/templates/go-backend/internal/core/apicontract/document_test.go +104 -0
  369. package/dist/resources/templates/go-backend/internal/core/apicontract/route_validator.go +78 -0
  370. package/dist/resources/templates/go-backend/internal/core/apicontract/route_validator_test.go +108 -0
  371. package/dist/resources/templates/go-backend/internal/core/apidocs/assets/scalar-api-reference.js +2385 -0
  372. package/dist/resources/templates/go-backend/internal/core/apidocs/assets/scalar-api-reference.metadata.json +10 -0
  373. package/dist/resources/templates/go-backend/internal/core/apidocs/route.go +107 -0
  374. package/dist/resources/templates/go-backend/internal/core/apidocs/route_test.go +200 -0
  375. package/dist/resources/templates/go-backend/internal/core/apidocs/scalar.go +117 -0
  376. package/dist/resources/templates/go-backend/internal/core/apidocs/scalar_test.go +99 -0
  377. package/dist/resources/templates/go-backend/internal/core/audit/access_log_policy.go +91 -0
  378. package/dist/resources/templates/go-backend/internal/core/audit/access_log_policy_test.go +51 -0
  379. package/dist/resources/templates/go-backend/internal/core/audit/actor_resolver.go +36 -0
  380. package/dist/resources/templates/go-backend/internal/core/audit/constants.go +30 -0
  381. package/dist/resources/templates/go-backend/internal/core/audit/context.go +119 -0
  382. package/dist/resources/templates/go-backend/internal/core/audit/context_test.go +310 -0
  383. package/dist/resources/templates/go-backend/internal/core/audit/dto.go +468 -0
  384. package/dist/resources/templates/go-backend/internal/core/audit/dto_admin.go +172 -0
  385. package/dist/resources/templates/go-backend/internal/core/audit/enum.go +460 -0
  386. package/dist/resources/templates/go-backend/internal/core/audit/enum_test.go +314 -0
  387. package/dist/resources/templates/go-backend/internal/core/audit/errors.go +9 -0
  388. package/dist/resources/templates/go-backend/internal/core/audit/handler.go +568 -0
  389. package/dist/resources/templates/go-backend/internal/core/audit/listener.go +319 -0
  390. package/dist/resources/templates/go-backend/internal/core/audit/listener_test.go +98 -0
  391. package/dist/resources/templates/go-backend/internal/core/audit/middleware.go +669 -0
  392. package/dist/resources/templates/go-backend/internal/core/audit/middleware_test.go +268 -0
  393. package/dist/resources/templates/go-backend/internal/core/audit/model.go +306 -0
  394. package/dist/resources/templates/go-backend/internal/core/audit/partition_job.go +316 -0
  395. package/dist/resources/templates/go-backend/internal/core/audit/ports.go +9 -0
  396. package/dist/resources/templates/go-backend/internal/core/audit/provider.go +30 -0
  397. package/dist/resources/templates/go-backend/internal/core/audit/repository.go +1007 -0
  398. package/dist/resources/templates/go-backend/internal/core/audit/repository_test.go +934 -0
  399. package/dist/resources/templates/go-backend/internal/core/audit/router.go +85 -0
  400. package/dist/resources/templates/go-backend/internal/core/audit/service.go +770 -0
  401. package/dist/resources/templates/go-backend/internal/core/audit/service_test.go +331 -0
  402. package/dist/resources/templates/go-backend/internal/core/audit/vo_admin.go +475 -0
  403. package/dist/resources/templates/go-backend/internal/core/cache/config.go +30 -0
  404. package/dist/resources/templates/go-backend/internal/core/cache/key.go +41 -0
  405. package/dist/resources/templates/go-backend/internal/core/cache/key_test.go +62 -0
  406. package/dist/resources/templates/go-backend/internal/core/cache/l1.go +153 -0
  407. package/dist/resources/templates/go-backend/internal/core/cache/l1_test.go +212 -0
  408. package/dist/resources/templates/go-backend/internal/core/cache/manager.go +278 -0
  409. package/dist/resources/templates/go-backend/internal/core/cache/manager_provider.go +24 -0
  410. package/dist/resources/templates/go-backend/internal/core/cache/metrics.go +126 -0
  411. package/dist/resources/templates/go-backend/internal/core/cache/namespace.go +46 -0
  412. package/dist/resources/templates/go-backend/internal/core/cache/namespace_test.go +90 -0
  413. package/dist/resources/templates/go-backend/internal/core/cache/options.go +39 -0
  414. package/dist/resources/templates/go-backend/internal/core/cache/options_test.go +96 -0
  415. package/dist/resources/templates/go-backend/internal/core/cache/redis.go +311 -0
  416. package/dist/resources/templates/go-backend/internal/core/cache/redis_test.go +51 -0
  417. package/dist/resources/templates/go-backend/internal/core/cache/serializer.go +31 -0
  418. package/dist/resources/templates/go-backend/internal/core/cache/serializer_test.go +139 -0
  419. package/dist/resources/templates/go-backend/internal/core/cache/singleflight.go +53 -0
  420. package/dist/resources/templates/go-backend/internal/core/cache/singleflight_test.go +211 -0
  421. package/dist/resources/templates/go-backend/internal/core/cache/warming.go +86 -0
  422. package/dist/resources/templates/go-backend/internal/core/clock/clock.go +66 -0
  423. package/dist/resources/templates/go-backend/internal/core/clock/clock_test.go +26 -0
  424. package/dist/resources/templates/go-backend/internal/core/clock/runtime_clock.go +283 -0
  425. package/dist/resources/templates/go-backend/internal/core/clock/runtime_clock_test.go +98 -0
  426. package/dist/resources/templates/go-backend/internal/core/commandreplay/errors.go +6 -0
  427. package/dist/resources/templates/go-backend/internal/core/commandreplay/model.go +90 -0
  428. package/dist/resources/templates/go-backend/internal/core/commandreplay/model_test.go +79 -0
  429. package/dist/resources/templates/go-backend/internal/core/commandreplay/namespace.go +7 -0
  430. package/dist/resources/templates/go-backend/internal/core/commandreplay/persistence.go +269 -0
  431. package/dist/resources/templates/go-backend/internal/core/commandreplay/persistence_test.go +185 -0
  432. package/dist/resources/templates/go-backend/internal/core/commandreplay/provider.go +9 -0
  433. package/dist/resources/templates/go-backend/internal/core/commandreplay/repository.go +22 -0
  434. package/dist/resources/templates/go-backend/internal/core/commandreplay/service.go +853 -0
  435. package/dist/resources/templates/go-backend/internal/core/commandreplay/service_persistence_test.go +432 -0
  436. package/dist/resources/templates/go-backend/internal/core/commandreplay/service_test.go +891 -0
  437. package/dist/resources/templates/go-backend/internal/core/commandreplay/types.go +121 -0
  438. package/dist/resources/templates/go-backend/internal/core/config/config.go +2069 -0
  439. package/dist/resources/templates/go-backend/internal/core/config/config_test.go +1122 -0
  440. package/dist/resources/templates/go-backend/internal/core/config/contract/contract_test.go +388 -0
  441. package/dist/resources/templates/go-backend/internal/core/config/contract/docs.go +198 -0
  442. package/dist/resources/templates/go-backend/internal/core/config/contract/verify.go +424 -0
  443. package/dist/resources/templates/go-backend/internal/core/config/enum.go +46 -0
  444. package/dist/resources/templates/go-backend/internal/core/config/metadata.go +717 -0
  445. package/dist/resources/templates/go-backend/internal/core/config/metadata_test.go +228 -0
  446. package/dist/resources/templates/go-backend/internal/core/config/product_admin_config_test.go +210 -0
  447. package/dist/resources/templates/go-backend/internal/core/crypto/aes_gcm.go +111 -0
  448. package/dist/resources/templates/go-backend/internal/core/crypto/aes_gcm_test.go +364 -0
  449. package/dist/resources/templates/go-backend/internal/core/crypto/encryptor.go +12 -0
  450. package/dist/resources/templates/go-backend/internal/core/crypto/envelope_encryptor.go +134 -0
  451. package/dist/resources/templates/go-backend/internal/core/crypto/envelope_encryptor_test.go +118 -0
  452. package/dist/resources/templates/go-backend/internal/core/crypto/pem.go +112 -0
  453. package/dist/resources/templates/go-backend/internal/core/crypto/pem_test.go +44 -0
  454. package/dist/resources/templates/go-backend/internal/core/crypto/provider.go +130 -0
  455. package/dist/resources/templates/go-backend/internal/core/crypto/provider_test.go +92 -0
  456. package/dist/resources/templates/go-backend/internal/core/database/base_model.go +24 -0
  457. package/dist/resources/templates/go-backend/internal/core/database/clickhouse.go +121 -0
  458. package/dist/resources/templates/go-backend/internal/core/database/database.go +112 -0
  459. package/dist/resources/templates/go-backend/internal/core/database/database_logger_test.go +95 -0
  460. package/dist/resources/templates/go-backend/internal/core/database/errors.go +25 -0
  461. package/dist/resources/templates/go-backend/internal/core/database/metrics_plugin.go +105 -0
  462. package/dist/resources/templates/go-backend/internal/core/database/pool_collector.go +71 -0
  463. package/dist/resources/templates/go-backend/internal/core/database/read_replica.go +520 -0
  464. package/dist/resources/templates/go-backend/internal/core/database/transaction.go +170 -0
  465. package/dist/resources/templates/go-backend/internal/core/database/transaction_test.go +282 -0
  466. package/dist/resources/templates/go-backend/internal/core/distlock/lock.go +353 -0
  467. package/dist/resources/templates/go-backend/internal/core/distlock/lock_test.go +106 -0
  468. package/dist/resources/templates/go-backend/internal/core/errors/handler.go +189 -0
  469. package/dist/resources/templates/go-backend/internal/core/errors/handler_test.go +515 -0
  470. package/dist/resources/templates/go-backend/internal/core/eventbus/eventbus.go +667 -0
  471. package/dist/resources/templates/go-backend/internal/core/eventbus/eventbus_test.go +792 -0
  472. package/dist/resources/templates/go-backend/internal/core/eventstream/noop.go +22 -0
  473. package/dist/resources/templates/go-backend/internal/core/eventstream/provider.go +20 -0
  474. package/dist/resources/templates/go-backend/internal/core/eventstream/publisher.go +38 -0
  475. package/dist/resources/templates/go-backend/internal/core/eventstream/redis.go +109 -0
  476. package/dist/resources/templates/go-backend/internal/core/eventstream/routing.go +104 -0
  477. package/dist/resources/templates/go-backend/internal/core/eventstream/routing_test.go +71 -0
  478. package/dist/resources/templates/go-backend/internal/core/geoip/model.go +19 -0
  479. package/dist/resources/templates/go-backend/internal/core/geoip/service.go +88 -0
  480. package/dist/resources/templates/go-backend/internal/core/health/health.go +333 -0
  481. package/dist/resources/templates/go-backend/internal/core/health/health_test.go +27 -0
  482. package/dist/resources/templates/go-backend/internal/core/httpclient/client.go +772 -0
  483. package/dist/resources/templates/go-backend/internal/core/httpclient/client_test.go +287 -0
  484. package/dist/resources/templates/go-backend/internal/core/httpclient/download.go +578 -0
  485. package/dist/resources/templates/go-backend/internal/core/httpclient/download_test.go +374 -0
  486. package/dist/resources/templates/go-backend/internal/core/jwtkeyring/jwk.go +38 -0
  487. package/dist/resources/templates/go-backend/internal/core/jwtkeyring/keyring.go +144 -0
  488. package/dist/resources/templates/go-backend/internal/core/jwtkeyring/keyring_test.go +128 -0
  489. package/dist/resources/templates/go-backend/internal/core/jwtkeyring/registry.go +60 -0
  490. package/dist/resources/templates/go-backend/internal/core/logger/logger.go +53 -0
  491. package/dist/resources/templates/go-backend/internal/core/logger/logger_test.go +194 -0
  492. package/dist/resources/templates/go-backend/internal/core/observability/metrics.go +358 -0
  493. package/dist/resources/templates/go-backend/internal/core/observability/sentry.go +445 -0
  494. package/dist/resources/templates/go-backend/internal/core/observability/sentry_privacy_test.go +175 -0
  495. package/dist/resources/templates/go-backend/internal/core/observability/tracing.go +187 -0
  496. package/dist/resources/templates/go-backend/internal/core/outbox/model.go +314 -0
  497. package/dist/resources/templates/go-backend/internal/core/outbox/model_test.go +77 -0
  498. package/dist/resources/templates/go-backend/internal/core/outbox/persistence.go +51 -0
  499. package/dist/resources/templates/go-backend/internal/core/outbox/provider.go +23 -0
  500. package/dist/resources/templates/go-backend/internal/core/outbox/relay.go +1138 -0
  501. package/dist/resources/templates/go-backend/internal/core/outbox/relay_postgres_test.go +257 -0
  502. package/dist/resources/templates/go-backend/internal/core/outbox/relay_test.go +690 -0
  503. package/dist/resources/templates/go-backend/internal/core/outbox/rescheduler.go +164 -0
  504. package/dist/resources/templates/go-backend/internal/core/outbox/rescheduler_test.go +209 -0
  505. package/dist/resources/templates/go-backend/internal/core/outbox/writer.go +249 -0
  506. package/dist/resources/templates/go-backend/internal/core/outbox/writer_postgres_test.go +170 -0
  507. package/dist/resources/templates/go-backend/internal/core/outbox/writer_test.go +123 -0
  508. package/dist/resources/templates/go-backend/internal/core/provider.go +198 -0
  509. package/dist/resources/templates/go-backend/internal/core/queue/metrics_middleware.go +38 -0
  510. package/dist/resources/templates/go-backend/internal/core/queue/metrics_middleware_test.go +12 -0
  511. package/dist/resources/templates/go-backend/internal/core/queue/queue.go +288 -0
  512. package/dist/resources/templates/go-backend/internal/core/queue/queue_test.go +159 -0
  513. package/dist/resources/templates/go-backend/internal/core/resilience/bulkhead.go +514 -0
  514. package/dist/resources/templates/go-backend/internal/core/resilience/bulkhead_test.go +984 -0
  515. package/dist/resources/templates/go-backend/internal/core/resilience/circuitbreaker.go +358 -0
  516. package/dist/resources/templates/go-backend/internal/core/resilience/circuitbreaker_test.go +788 -0
  517. package/dist/resources/templates/go-backend/internal/core/resilience/retry.go +318 -0
  518. package/dist/resources/templates/go-backend/internal/core/resilience/retry_test.go +735 -0
  519. package/dist/resources/templates/go-backend/internal/core/schedule/recorder.go +305 -0
  520. package/dist/resources/templates/go-backend/internal/core/schedule/run_context.go +124 -0
  521. package/dist/resources/templates/go-backend/internal/core/schedule/run_context_test.go +85 -0
  522. package/dist/resources/templates/go-backend/internal/core/schedule/scheduler.go +1589 -0
  523. package/dist/resources/templates/go-backend/internal/core/schedule/scheduler_control_test.go +808 -0
  524. package/dist/resources/templates/go-backend/internal/core/storage/oss_provider.go +398 -0
  525. package/dist/resources/templates/go-backend/internal/core/storage/oss_provider_test.go +89 -0
  526. package/dist/resources/templates/go-backend/internal/core/storage/path.go +110 -0
  527. package/dist/resources/templates/go-backend/internal/core/storage/path_test.go +301 -0
  528. package/dist/resources/templates/go-backend/internal/core/storage/s3_provider.go +489 -0
  529. package/dist/resources/templates/go-backend/internal/core/storage/s3_provider_test.go +60 -0
  530. package/dist/resources/templates/go-backend/internal/core/storage/storage.go +268 -0
  531. package/dist/resources/templates/go-backend/internal/core/storage/validation.go +225 -0
  532. package/dist/resources/templates/go-backend/internal/core/storage/validation_test.go +495 -0
  533. package/dist/resources/templates/go-backend/internal/core/temporal/logger.go +38 -0
  534. package/dist/resources/templates/go-backend/internal/core/temporal/provider.go +126 -0
  535. package/dist/resources/templates/go-backend/internal/core/temporal/provider_test.go +67 -0
  536. package/dist/resources/templates/go-backend/internal/core/websocket/client.go +282 -0
  537. package/dist/resources/templates/go-backend/internal/core/websocket/client_test.go +81 -0
  538. package/dist/resources/templates/go-backend/internal/core/websocket/config.go +58 -0
  539. package/dist/resources/templates/go-backend/internal/core/websocket/handler.go +53 -0
  540. package/dist/resources/templates/go-backend/internal/core/websocket/hub.go +269 -0
  541. package/dist/resources/templates/go-backend/internal/core/websocket/message.go +273 -0
  542. package/dist/resources/templates/go-backend/internal/core/websocket/notifier.go +92 -0
  543. package/dist/resources/templates/go-backend/internal/core/websocket/provider.go +33 -0
  544. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/action.go +77 -0
  545. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/action_test.go +238 -0
  546. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/condition.go +107 -0
  547. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/decision.go +256 -0
  548. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/enum.go +643 -0
  549. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/errors.go +102 -0
  550. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/principal_record.go +21 -0
  551. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/product_context.go +486 -0
  552. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/resource.go +215 -0
  553. package/dist/resources/templates/go-backend/internal/integrations/onex/iam/resource_test.go +242 -0
  554. package/dist/resources/templates/go-backend/internal/products/README.md +5 -0
  555. package/dist/resources/templates/go-backend/internal/sharedevents/README.md +5 -0
  556. package/dist/resources/templates/go-backend/migrations/000001_infrastructure.down.sql +13 -0
  557. package/dist/resources/templates/go-backend/migrations/000001_infrastructure.up.sql +886 -0
  558. package/dist/resources/templates/go-backend/pkg/apierror/error.go +327 -0
  559. package/dist/resources/templates/go-backend/pkg/apierror/error_code.go +65 -0
  560. package/dist/resources/templates/go-backend/pkg/apierror/error_test.go +41 -0
  561. package/dist/resources/templates/go-backend/pkg/apierror/error_type.go +14 -0
  562. package/dist/resources/templates/go-backend/pkg/apierror/http_response.go +216 -0
  563. package/dist/resources/templates/go-backend/pkg/apierror/http_response_test.go +216 -0
  564. package/dist/resources/templates/go-backend/scripts/check-openapi-clean-deletion.sh +54 -0
  565. package/dist/resources/templates/go-backend/scripts/test-migrations.sh +32 -0
  566. package/dist/resources/templates/go-backend/tests/infrastructure.sql +69 -0
  567. package/dist/resources/templates/mcp-server/.claude/docs/stack.md +338 -0
  568. package/dist/resources/templates/mcp-server/.editorconfig +13 -0
  569. package/dist/resources/templates/mcp-server/.env.example +8 -0
  570. package/dist/resources/templates/mcp-server/.github/ISSUE_TEMPLATE/bug_report.yml +104 -0
  571. package/dist/resources/templates/mcp-server/.github/ISSUE_TEMPLATE/feature_request.yml +54 -0
  572. package/dist/resources/templates/mcp-server/.github/PULL_REQUEST_TEMPLATE.md +36 -0
  573. package/dist/resources/templates/mcp-server/.github/workflows/ci.yml +78 -0
  574. package/dist/resources/templates/mcp-server/.husky/commit-msg +1 -0
  575. package/dist/resources/templates/mcp-server/.husky/pre-commit +1 -0
  576. package/dist/resources/templates/mcp-server/.lintstagedrc.json +4 -0
  577. package/dist/resources/templates/mcp-server/.nvmrc +1 -0
  578. package/dist/resources/templates/mcp-server/.prettierrc +8 -0
  579. package/dist/resources/templates/mcp-server/CONTRIBUTING.md +184 -0
  580. package/dist/resources/templates/mcp-server/Dockerfile +23 -0
  581. package/dist/resources/templates/mcp-server/LICENSE +21 -0
  582. package/dist/resources/templates/mcp-server/README.md +123 -0
  583. package/dist/resources/templates/mcp-server/_gitignore +26 -0
  584. package/dist/resources/templates/mcp-server/commitlint.config.js +24 -0
  585. package/dist/resources/templates/mcp-server/eslint.config.js +30 -0
  586. package/dist/resources/templates/mcp-server/package.json +72 -0
  587. package/dist/resources/templates/mcp-server/src/index.ts +27 -0
  588. package/dist/resources/templates/mcp-server/src/resources.ts +33 -0
  589. package/dist/resources/templates/mcp-server/src/services/.gitkeep +0 -0
  590. package/dist/resources/templates/mcp-server/src/tools.ts +43 -0
  591. package/dist/resources/templates/mcp-server/src/types/index.ts +19 -0
  592. package/dist/resources/templates/mcp-server/src/utils/index.ts +31 -0
  593. package/dist/resources/templates/mcp-server/tsconfig.json +22 -0
  594. package/dist/resources/templates/mcp-server/tsup.config.ts +14 -0
  595. package/dist/resources/templates/mcp-server/vitest.config.ts +15 -0
  596. package/dist/resources/templates/nuxt/.claude/docs/stack.md +666 -0
  597. package/dist/resources/templates/nuxt/.editorconfig +15 -0
  598. package/dist/resources/templates/nuxt/.env.example +22 -0
  599. package/dist/resources/templates/nuxt/.github/workflows/ci.yml +34 -0
  600. package/dist/resources/templates/nuxt/.husky/commit-msg +1 -0
  601. package/dist/resources/templates/nuxt/.husky/pre-commit +1 -0
  602. package/dist/resources/templates/nuxt/.nvmrc +1 -0
  603. package/dist/resources/templates/nuxt/.prettierignore +9 -0
  604. package/dist/resources/templates/nuxt/.prettierrc +1 -0
  605. package/dist/resources/templates/nuxt/README.md +23 -0
  606. package/dist/resources/templates/nuxt/_gitignore +7 -0
  607. package/dist/resources/templates/nuxt/app/app.config.ts +105 -0
  608. package/dist/resources/templates/nuxt/app/i18n/locales/en.json +5 -0
  609. package/dist/resources/templates/nuxt/app/pages/app/index.vue +224 -0
  610. package/dist/resources/templates/nuxt/app/pages/authors/[slug].vue +37 -0
  611. package/dist/resources/templates/nuxt/app/pages/blog/[slug].vue +55 -0
  612. package/dist/resources/templates/nuxt/app/pages/blog/category/[category].vue +38 -0
  613. package/dist/resources/templates/nuxt/app/pages/blog/index.vue +46 -0
  614. package/dist/resources/templates/nuxt/app/pages/index.vue +626 -0
  615. package/dist/resources/templates/nuxt/app/pages/legal/privacy.vue +21 -0
  616. package/dist/resources/templates/nuxt/app/pages/legal/terms.vue +21 -0
  617. package/dist/resources/templates/nuxt/app/pages/ui/index.vue +397 -0
  618. package/dist/resources/templates/nuxt/commitlint.config.mjs +1 -0
  619. package/dist/resources/templates/nuxt/eslint.config.mjs +4 -0
  620. package/dist/resources/templates/nuxt/lint-staged.config.mjs +8 -0
  621. package/dist/resources/templates/nuxt/nuxt.config.ts +71 -0
  622. package/dist/resources/templates/nuxt/package.json +67 -0
  623. package/dist/resources/templates/nuxt/public/apple-touch-icon.png +0 -0
  624. package/dist/resources/templates/nuxt/public/favicon.ico +0 -0
  625. package/dist/resources/templates/nuxt/public/favicon.svg +5 -0
  626. package/dist/resources/templates/nuxt/public/icon-16x16.png +0 -0
  627. package/dist/resources/templates/nuxt/public/icon-192x192.png +0 -0
  628. package/dist/resources/templates/nuxt/public/icon-32x32.png +0 -0
  629. package/dist/resources/templates/nuxt/public/icon-512x512.png +0 -0
  630. package/dist/resources/templates/nuxt/public/logo.svg +6 -0
  631. package/dist/resources/templates/nuxt/public/media/hero-product-gallery.png +0 -0
  632. package/dist/resources/templates/nuxt/server/api/content/sitemap-urls.get.ts +12 -0
  633. package/dist/resources/templates/nuxt/server/api/preview.get.ts +9 -0
  634. package/dist/resources/templates/nuxt/stylelint.config.mjs +3 -0
  635. package/dist/resources/templates/nuxt/test/e2e/rendering.test.ts +13 -0
  636. package/dist/resources/templates/nuxt/tsconfig.json +3 -0
  637. package/dist/resources/templates/nuxt/vitest.config.ts +8 -0
  638. package/package.json +8 -5
@@ -0,0 +1,2069 @@
1
+ package config
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "io"
7
+ "net/netip"
8
+ "net/url"
9
+ "os"
10
+ "regexp"
11
+ "strings"
12
+ "time"
13
+ "unicode"
14
+
15
+ "github.com/spf13/viper"
16
+ "github.com/subosito/gotenv"
17
+
18
+ "{{module}}/internal/common/canonicaljson"
19
+ "{{module}}/internal/common/serviceidentity"
20
+ )
21
+
22
+ const (
23
+ defaultInternalServiceTokenTTLSeconds = 120
24
+ maxInternalServiceTokenTTLSeconds = 900
25
+ defaultIAMConsistencyTokenTTLSeconds = 120
26
+ maxIAMConsistencyTokenTTLSeconds = 300
27
+ defaultDelegatedAccessTokenTTLSeconds = 300
28
+ maxDelegatedAccessTokenTTLSeconds = 900
29
+ defaultIdentityClockSkewLeeway = 30 * time.Second
30
+ maxIdentityClockSkewLeeway = time.Minute
31
+ )
32
+
33
+ // Config 应用配置,聚合所有子配置
34
+ type Config struct {
35
+ App AppConfig `mapstructure:"app"`
36
+ Database DatabaseConfig `mapstructure:"database"`
37
+ ClickHouse ClickHouseConfig `mapstructure:"clickhouse"`
38
+ Redis RedisConfig `mapstructure:"redis"`
39
+ OpenAI OpenAIConfig `mapstructure:"openai"`
40
+ Anthropic AnthropicConfig `mapstructure:"anthropic"`
41
+ Kafka KafkaConfig `mapstructure:"kafka"`
42
+ Log LogConfig `mapstructure:"log"`
43
+ Audit AuditConfig `mapstructure:"audit"`
44
+ Throttle ThrottleConfig `mapstructure:"throttle"`
45
+ Export ExportConfig `mapstructure:"export"`
46
+ Tracing TracingConfig `mapstructure:"tracing"`
47
+ // AES-256-GCM 加密密钥(64 位 hex 字符串)
48
+ EncryptionKey string `mapstructure:"encryption_key"`
49
+ EncryptionKeyID string `mapstructure:"encryption_key_id"`
50
+ EncryptionKeyringFile string `mapstructure:"encryption_keyring_file"`
51
+ ClamAV ClamAVConfig `mapstructure:"clamav"`
52
+ Storage StorageConfig `mapstructure:"storage"`
53
+ Documentation DocumentationConfig `mapstructure:"documentation"`
54
+ APIContract APIContractConfig `mapstructure:"api_contract"`
55
+ Sentry SentryConfig `mapstructure:"sentry"`
56
+ GeoIP GeoIPConfig `mapstructure:"geoip"`
57
+ AI AIConfig `mapstructure:"ai"`
58
+ ContentSafety ContentSafetyConfig `mapstructure:"content_safety"`
59
+ Communication CommunicationConfig `mapstructure:"communication"`
60
+ Payment PaymentConfig `mapstructure:"payment"`
61
+ Checkout CheckoutConfig `mapstructure:"checkout"`
62
+ Identity IdentityConfig `mapstructure:"identity"`
63
+ FeatureFlag FeatureFlagConfig `mapstructure:"feature_flag"`
64
+ Secret SecretConfig `mapstructure:"secret"`
65
+ // IntegrationEvent 定义跨产品 Integration Event 接收端的运行时证据与信任边界。
66
+ IntegrationEvent IntegrationEventConfig `mapstructure:"integration_event"`
67
+ // 内部服务调用配置
68
+ InternalService InternalServiceConfig `mapstructure:"internal_service"`
69
+ // 管理端体验外部产品贡献配置
70
+ AdminExperience AdminExperienceConfig `mapstructure:"admin_experience"`
71
+ // 外部产品管理控制面目标注册表
72
+ ProductAdmin ProductAdminConfig `mapstructure:"product_admin"`
73
+ // Temporal 工作流引擎
74
+ Temporal TemporalConfig `mapstructure:"temporal"`
75
+ // ExchangeRate-API 密钥(免费版)
76
+ ExchangeRateAPIKey string `mapstructure:"exchange_rate_api_key"`
77
+ }
78
+
79
+ // DatabaseCommandConfig 为只读数据库治理命令提供最小启动配置。
80
+ type DatabaseCommandConfig struct {
81
+ Database DatabaseConfig
82
+ Log LogConfig
83
+ }
84
+
85
+ // BootstrapCommandConfig 为配置初始化命令提供最小启动配置。
86
+ type BootstrapCommandConfig struct {
87
+ Database DatabaseConfig
88
+ Log LogConfig
89
+ EncryptionKey string
90
+ }
91
+
92
+ // SecretCutoverCommandConfig 为长期凭证迁移提供旧密文和 SecretBroker 驱动所需的最小配置。
93
+ type SecretCutoverCommandConfig struct {
94
+ EncryptionKey string
95
+ EncryptionKeyID string
96
+ EncryptionKeyringFile string
97
+ Secret SecretConfig
98
+ }
99
+
100
+ // AppConfig 应用基础配置
101
+ type AppConfig struct {
102
+ Name string `mapstructure:"name"`
103
+ Port int `mapstructure:"port"`
104
+ Host string `mapstructure:"host"`
105
+ Env string `mapstructure:"env"`
106
+ // 受信任的代理 IP(APISIX 网关地址),用于正确解析 X-Forwarded-For
107
+ TrustedProxies []string `mapstructure:"trusted_proxies"`
108
+ }
109
+
110
+ // DatabaseConfig 数据库配置
111
+ type DatabaseConfig struct {
112
+ Host string `mapstructure:"host"`
113
+ Port int `mapstructure:"port"`
114
+ Username string `mapstructure:"username"`
115
+ Password string `mapstructure:"password"`
116
+ Name string `mapstructure:"name"`
117
+ SSL bool `mapstructure:"ssl"`
118
+ Logging bool `mapstructure:"logging"`
119
+ MaxOpenConns int `mapstructure:"max_open_conns"`
120
+ MaxIdleConns int `mapstructure:"max_idle_conns"`
121
+ ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
122
+ ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"`
123
+ }
124
+
125
+ // ClickHouseConfig ClickHouse 列存配置
126
+ //
127
+ // 行业参考:clickhouse-go/v2 官方 Options 字段命名(Addr / Auth / DialTimeout
128
+ // / ReadTimeout / MaxOpenConns)。Insights 模块通过该配置接入列存分析能力。
129
+ type ClickHouseConfig struct {
130
+ // Enabled 控制是否在启动时创建 ClickHouse 连接。本地/CI 环境可关闭。
131
+ Enabled bool `mapstructure:"enabled"`
132
+ // Host ClickHouse 服务地址(host:port 中的 host 部分)
133
+ Host string `mapstructure:"host"`
134
+ // Port ClickHouse Native TCP 端口(默认 9000)
135
+ Port int `mapstructure:"port"`
136
+ // Database 默认数据库(默认 insights)
137
+ Database string `mapstructure:"database"`
138
+ // Username 用户名
139
+ Username string `mapstructure:"username"`
140
+ // Password 密码(生产从 env 注入)
141
+ Password string `mapstructure:"password"`
142
+ // DialTimeout 拨号超时
143
+ DialTimeout time.Duration `mapstructure:"dial_timeout"`
144
+ // ReadTimeout 单次查询读超时
145
+ ReadTimeout time.Duration `mapstructure:"read_timeout"`
146
+ // MaxOpenConns 最大连接数
147
+ MaxOpenConns int `mapstructure:"max_open_conns"`
148
+ // MaxIdleConns 最大空闲连接数
149
+ MaxIdleConns int `mapstructure:"max_idle_conns"`
150
+ // ConnMaxLifetime 连接最大生命周期
151
+ ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
152
+ // Compression 是否启用 LZ4 压缩(默认 true)
153
+ Compression bool `mapstructure:"compression"`
154
+ // Secure 是否启用 TLS(默认 false;ClickHouse Cloud 必须 true)
155
+ Secure bool `mapstructure:"secure"`
156
+ }
157
+
158
+ // RedisConfig Redis 配置
159
+ type RedisConfig struct {
160
+ Host string `mapstructure:"host"`
161
+ Port int `mapstructure:"port"`
162
+ Password string `mapstructure:"password"`
163
+ DB int `mapstructure:"db"`
164
+ }
165
+
166
+ // OpenAIConfig OpenAI 配置
167
+ type OpenAIConfig struct {
168
+ APIKey string `mapstructure:"api_key"`
169
+ Model string `mapstructure:"model"`
170
+ BaseURL string `mapstructure:"base_url"`
171
+ }
172
+
173
+ // AnthropicConfig Anthropic 配置
174
+ type AnthropicConfig struct {
175
+ APIKey string `mapstructure:"api_key"`
176
+ Model string `mapstructure:"model"`
177
+ }
178
+
179
+ // ContentSafetyConfig 内容安全 provider 配置。
180
+ type ContentSafetyConfig struct {
181
+ Aliyun AliyunContentSafetyConfig `mapstructure:"aliyun"`
182
+ }
183
+
184
+ // CommunicationConfig 通信平台进程级配置。
185
+ type CommunicationConfig struct {
186
+ Reputation CommunicationReputationConfig `mapstructure:"reputation"`
187
+ UnsubscribeSigningSecret string `mapstructure:"unsubscribe_signing_secret"`
188
+ }
189
+
190
+ // CommunicationReputationConfig 通信信誉检测配置。
191
+ type CommunicationReputationConfig struct {
192
+ IPBlocklistZones []string `mapstructure:"ip_blocklist_zones"`
193
+ DomainBlocklistZones []string `mapstructure:"domain_blocklist_zones"`
194
+ DNSLookupTimeoutSeconds int `mapstructure:"dns_lookup_timeout_seconds"`
195
+ RecheckIntervalHours int `mapstructure:"recheck_interval_hours"`
196
+ RecheckBatchLimit int `mapstructure:"recheck_batch_limit"`
197
+ }
198
+
199
+ // PaymentConfig 支付模块进程级配置。
200
+ type PaymentConfig struct {
201
+ // RiskSubjectHashKey 支付风控主体 HMAC-SHA256 密钥。
202
+ RiskSubjectHashKey string `mapstructure:"risk_subject_hash_key"`
203
+ // StorePurchaseReferenceHashKey 商店购买凭证 HMAC-SHA256 密钥。
204
+ StorePurchaseReferenceHashKey string `mapstructure:"store_purchase_reference_hash_key"`
205
+ }
206
+
207
+ // IdentityConfig 身份模块进程级配置。
208
+ type IdentityConfig struct {
209
+ // PhoneHashKey 手机号绑定 HMAC-SHA256 密钥。
210
+ PhoneHashKey string `mapstructure:"phone_hash_key"`
211
+ // OAuthStateSigningSecret OAuth 授权 state HMAC-SHA256 密钥。
212
+ OAuthStateSigningSecret string `mapstructure:"oauth_state_signing_secret"`
213
+ // Clients 注册一方客户端的 canonical 产品、主体类型与注册来源。
214
+ Clients map[string]IdentityClientConfig `mapstructure:"clients"`
215
+ // AccessToken 用户访问令牌授权服务器配置。
216
+ AccessToken IdentityAccessTokenConfig `mapstructure:"access_token"`
217
+ // GuestCapability 定义 Identity-owned guest capability 密钥材料来源与实例租约身份。
218
+ GuestCapability IdentityGuestCapabilityConfig `mapstructure:"guest_capability"`
219
+ // LegacyPasswordSchemes 注册仅用于登录后渐进升级的旧密码验证方案。
220
+ LegacyPasswordSchemes map[string]IdentityLegacyPasswordSchemeConfig `mapstructure:"legacy_password_schemes"`
221
+ }
222
+
223
+ // IdentityLegacyPasswordSchemeConfig 定义旧密码方案的驱动与受控密钥引用。
224
+ type IdentityLegacyPasswordSchemeConfig struct {
225
+ Driver string `mapstructure:"driver"`
226
+ SecretRef string `mapstructure:"secret_ref"`
227
+ RetireAt string `mapstructure:"retire_at"`
228
+ }
229
+
230
+ // IdentityClientConfig 定义一个已注册一方客户端的授权边界。
231
+ type IdentityClientConfig struct {
232
+ // ProductCode 是客户端唯一允许签发的 canonical 产品资源。
233
+ ProductCode string `mapstructure:"product_code" json:"product_code"`
234
+ // ApplicationCode 是 Product owner 中客户端应用的稳定编码。
235
+ ApplicationCode string `mapstructure:"application_code" json:"application_code"`
236
+ // AllowedPrincipalTypes 限定客户端可建立的会话主体类型。
237
+ AllowedPrincipalTypes []string `mapstructure:"allowed_principal_types" json:"allowed_principal_types"`
238
+ // RegistrationSource 记录身份首次物化的业务入口。
239
+ RegistrationSource string `mapstructure:"registration_source" json:"registration_source"`
240
+ // TokenExchangeServiceAccounts 限定可代表此客户端执行 RFC 8693 交换的机密服务账号。
241
+ TokenExchangeServiceAccounts []string `mapstructure:"token_exchange_service_accounts" json:"token_exchange_service_accounts"`
242
+ // TokenExchangeResources 限定此客户端可交换到的精确资源服务器受众。
243
+ TokenExchangeResources []string `mapstructure:"token_exchange_resources" json:"token_exchange_resources"`
244
+ }
245
+
246
+ // FeatureFlagConfig 定义已发布快照的新鲜度和有界陈旧策略。
247
+ type FeatureFlagConfig struct {
248
+ FreshnessTTL time.Duration `mapstructure:"freshness_ttl"`
249
+ MaximumStaleAge time.Duration `mapstructure:"maximum_stale_age"`
250
+ CacheTTL time.Duration `mapstructure:"cache_ttl"`
251
+ }
252
+
253
+ // IdentityGuestCapabilityConfig 只引用进程本地秘密文件;活动 generation 由数据库注册表决定。
254
+ type IdentityGuestCapabilityConfig struct {
255
+ SecretProviderFile string `mapstructure:"secret_provider_file"`
256
+ // InitialKeyID 仅在注册表为空时注册为首个签发密钥。
257
+ InitialKeyID string `mapstructure:"initial_key_id"`
258
+ VerifierInstanceID string `mapstructure:"verifier_instance_id"`
259
+ }
260
+
261
+ // IdentityAccessTokenConfig 定义 RFC 9068 访问令牌签发与校验边界。
262
+ type IdentityAccessTokenConfig struct {
263
+ Issuer string `mapstructure:"issuer"`
264
+ SigningKeyID string `mapstructure:"signing_key_id"`
265
+ PrivateKeyPath string `mapstructure:"private_key_path"`
266
+ PublicKeyDirectory string `mapstructure:"public_key_directory"`
267
+ ClockSkewLeeway time.Duration `mapstructure:"clock_skew_leeway"`
268
+ DelegatedTokenTTL time.Duration `mapstructure:"delegated_token_ttl"`
269
+ ProtectedResources map[string]IdentityProtectedResourceConfig `mapstructure:"protected_resources"`
270
+ Resources map[string]IdentityAccessTokenResourceConfig `mapstructure:"resources"`
271
+ }
272
+
273
+ // IdentityProtectedResourceConfig 定义 RFC 9728 资源标识到内部精确 audience 的映射。
274
+ type IdentityProtectedResourceConfig struct {
275
+ ResourceIdentifier string `mapstructure:"resource_identifier" json:"resource_identifier"`
276
+ Audience string `mapstructure:"audience" json:"audience"`
277
+ ResourceName string `mapstructure:"resource_name" json:"resource_name"`
278
+ }
279
+
280
+ // IdentityAccessTokenResourceConfig 定义单个产品资源服务器的 audience 与主体 scope 上限。
281
+ type IdentityAccessTokenResourceConfig struct {
282
+ Audience string `mapstructure:"audience"`
283
+ UserScopes []string `mapstructure:"user_scopes"`
284
+ GuestScopes []string `mapstructure:"guest_scopes"`
285
+ }
286
+
287
+ // IntegrationEventConfig 定义 Integration Event 平台进程配置。
288
+ type IntegrationEventConfig struct {
289
+ Inbound IntegrationEventInboundConfig `mapstructure:"inbound"`
290
+ }
291
+
292
+ // IntegrationEventInboundConfig 定义入站接收端的只读信任与发布证据文件。
293
+ type IntegrationEventInboundConfig struct {
294
+ TrustRegistryFile string `mapstructure:"trust_registry_file"`
295
+ RuntimeArtifactFile string `mapstructure:"runtime_artifact_file"`
296
+ ReleaseManifestFile string `mapstructure:"release_manifest_file"`
297
+ MigrationMarkerFile string `mapstructure:"migration_marker_file"`
298
+ }
299
+
300
+ // InternalServiceConfig 内部服务调用配置。
301
+ type InternalServiceConfig struct {
302
+ // PlatformBaseURL platform-api 内部 API 根地址。
303
+ PlatformBaseURL string `mapstructure:"platform_base_url"`
304
+ // DocumentationBaseURL documentation-api 机器读取 API 根地址。
305
+ DocumentationBaseURL string `mapstructure:"documentation_base_url"`
306
+ // ServiceTokenSigning 定义 IAM 服务令牌独立 RSA 密钥域。
307
+ ServiceTokenSigning ServiceTokenSigningConfig `mapstructure:"service_token_signing"`
308
+ // OutboundIdentities 按 owner 自定义名称声明独立发送身份与目标资源策略。
309
+ OutboundIdentities map[string]InternalServiceIdentityConfig `mapstructure:"outbound_identities"`
310
+ // TokenTTLSeconds 内部服务令牌有效期秒数。
311
+ TokenTTLSeconds int `mapstructure:"token_ttl_seconds"`
312
+ // AuthorizationConsistencyTokenTTLSeconds IAM 权威裁决一致性令牌有效期秒数。
313
+ AuthorizationConsistencyTokenTTLSeconds int `mapstructure:"authorization_consistency_token_ttl_seconds"`
314
+ // RequestTimeout 限制内部服务请求与令牌交换时长。
315
+ RequestTimeout time.Duration `mapstructure:"request_timeout"`
316
+ }
317
+
318
+ // ServiceTokenSigningConfig 定义 IAM 服务令牌 RSA 签名与验签材料。
319
+ type ServiceTokenSigningConfig struct {
320
+ KeyID string `mapstructure:"key_id"`
321
+ PrivateKeyPath string `mapstructure:"private_key_path"`
322
+ PublicKeyDirectory string `mapstructure:"public_key_directory"`
323
+ }
324
+
325
+ // InternalServiceIdentityConfig 定义单个发送身份到目标资源的不可变策略。
326
+ type InternalServiceIdentityConfig struct {
327
+ CredentialRef string `mapstructure:"credential_ref" json:"credential_ref"`
328
+ AllowedProductCodes []string `mapstructure:"allowed_product_codes" json:"allowed_product_codes"`
329
+ Audience string `mapstructure:"audience" json:"audience"`
330
+ ExpectedServiceAccountCode string `mapstructure:"expected_service_account_code" json:"expected_service_account_code"`
331
+ }
332
+
333
+ // AdminExperienceConfig 管理独立部署产品的数据集贡献端点。
334
+ type AdminExperienceConfig struct {
335
+ ExternalProducts []AdminExperienceExternalProductConfig `mapstructure:"external_products"`
336
+ }
337
+
338
+ // AdminExperienceExternalProductConfig 声明一个外部产品的受信端点和固定数据集。
339
+ type AdminExperienceExternalProductConfig struct {
340
+ ProductCode string `mapstructure:"product_code"`
341
+ BaseURL string `mapstructure:"base_url"`
342
+ Datasets []AdminExperienceExternalDatasetConfig `mapstructure:"datasets"`
343
+ }
344
+
345
+ // AdminExperienceExternalDatasetConfig 声明一个外部产品数据集契约。
346
+ type AdminExperienceExternalDatasetConfig struct {
347
+ Key string `mapstructure:"key"`
348
+ DescriptorHash string `mapstructure:"descriptor_hash"`
349
+ }
350
+
351
+ // ProductAdminConfig 按产品编码注册外部产品管理控制面。
352
+ type ProductAdminConfig struct {
353
+ Targets map[string]ProductAdminTargetConfig `mapstructure:"targets"`
354
+ }
355
+
356
+ // ProductAdminTargetConfig 定义一个外部产品管理控制面的受信网络与服务身份边界。
357
+ type ProductAdminTargetConfig struct {
358
+ AllowedOrigin string `mapstructure:"allowed_origin"`
359
+ BaseURL string `mapstructure:"base_url"`
360
+ RequestTimeout time.Duration `mapstructure:"request_timeout"`
361
+ ServiceIdentityName string `mapstructure:"service_identity_name"`
362
+ SignatureKeyID string `mapstructure:"signature_key_id"`
363
+ SignatureSecretRef string `mapstructure:"signature_secret_ref"`
364
+ }
365
+
366
+ type productAdminTargetEnvironmentConfig struct {
367
+ AllowedOrigin string `json:"allowed_origin"`
368
+ BaseURL string `json:"base_url"`
369
+ RequestTimeout string `json:"request_timeout"`
370
+ ServiceIdentityName string `json:"service_identity_name"`
371
+ SignatureKeyID string `json:"signature_key_id"`
372
+ SignatureSecretRef string `json:"signature_secret_ref"`
373
+ }
374
+
375
+ // CheckoutConfig 客户在场结账的进程级安全配置。
376
+ type CheckoutConfig struct {
377
+ // MethodReferenceSigningKey 为短期支付方式引用提供独立 HMAC-SHA256 密钥。
378
+ MethodReferenceSigningKey string `mapstructure:"method_reference_signing_key"`
379
+ // GuestActorScopeSigningKey 为匿名结账幂等主体范围提供独立 HMAC-SHA256 密钥。
380
+ GuestActorScopeSigningKey string `mapstructure:"guest_actor_scope_signing_key"`
381
+ // ReplayEncryptionKeyVersion 标识当前幂等敏感响应使用的加密密钥版本。
382
+ ReplayEncryptionKeyVersion string `mapstructure:"replay_encryption_key_version"`
383
+ }
384
+
385
+ // SecretConfig 定义 SecretBroker 驱动、指纹和 worker 启动配置。
386
+ type SecretConfig struct {
387
+ ActiveDriverCode string `mapstructure:"active_driver_code"`
388
+ FingerprintKey string `mapstructure:"fingerprint_key"`
389
+ DriverHealthTimeout time.Duration `mapstructure:"driver_health_timeout"`
390
+ Envelope SecretEnvelopeDriverConfig `mapstructure:"envelope"`
391
+ Runtime SecretRuntimeIdentityConfig `mapstructure:"runtime"`
392
+ Retention SecretRetentionConfig `mapstructure:"retention"`
393
+ Worker SecretWorkerConfig `mapstructure:"worker"`
394
+ }
395
+
396
+ type SecretEnvelopeDriverConfig struct {
397
+ DriverCode string `mapstructure:"driver_code"`
398
+ MaximumPlaintextBytes int `mapstructure:"maximum_plaintext_bytes"`
399
+ }
400
+
401
+ // SecretRuntimeIdentityConfig 定义当前进程兑换短时 Secret 租约时使用的 IAM 工作负载身份。
402
+ type SecretRuntimeIdentityConfig struct {
403
+ ServiceAccountCode string `mapstructure:"service_account_code"`
404
+ Audience string `mapstructure:"audience"`
405
+ DefaultProductCode string `mapstructure:"default_product_code"`
406
+ LeaseTTL time.Duration `mapstructure:"lease_ttl"`
407
+ }
408
+
409
+ // SecretRetentionConfig 定义密文销毁和访问证据清理的保留边界。
410
+ type SecretRetentionConfig struct {
411
+ MinimumDestructionDelay time.Duration `mapstructure:"minimum_destruction_delay"`
412
+ AccessEventPurgeEnabled bool `mapstructure:"access_event_purge_enabled"`
413
+ LegalHoldOwnerResources []string `mapstructure:"legal_hold_owner_resources"`
414
+ }
415
+
416
+ type SecretWorkerConfig struct {
417
+ ServiceAccountCode string `mapstructure:"service_account_code"`
418
+ BatchSize int `mapstructure:"batch_size"`
419
+ PartitionMonthsAhead int `mapstructure:"partition_months_ahead"`
420
+ }
421
+
422
+ // AliyunContentSafetyConfig 阿里云内容安全 2.0 / AI 安全护栏配置。
423
+ type AliyunContentSafetyConfig struct {
424
+ // Enabled 控制是否注册阿里云安全检测器;false 时不会产生外部 API 调用。
425
+ Enabled bool `mapstructure:"enabled"`
426
+ // ProviderCode p_ai_providers 中保存加密 AK/SK 的 Provider 编码。
427
+ ProviderCode string `mapstructure:"provider_code"`
428
+ // RegionID 接入区域。
429
+ RegionID string `mapstructure:"region_id"`
430
+ // Endpoint 内容安全接入地址。
431
+ Endpoint string `mapstructure:"endpoint"`
432
+ // TextService 文本前置审核 service code。
433
+ TextService string `mapstructure:"text_service"`
434
+ // ImageService 图片后置审核 service code。
435
+ ImageService string `mapstructure:"image_service"`
436
+ // VideoService 视频后置审核 service code。
437
+ VideoService string `mapstructure:"video_service"`
438
+ // ConnectTimeoutMs SDK 连接超时,单位毫秒。
439
+ ConnectTimeoutMs int `mapstructure:"connect_timeout_ms"`
440
+ // ReadTimeoutMs SDK 读超时,单位毫秒。
441
+ ReadTimeoutMs int `mapstructure:"read_timeout_ms"`
442
+ // VideoPollInitialDelayMs 视频异步任务提交后首次查询等待时间,单位毫秒。
443
+ VideoPollInitialDelayMs int `mapstructure:"video_poll_initial_delay_ms"`
444
+ // VideoPollIntervalMs 视频异步结果轮询间隔,单位毫秒。
445
+ VideoPollIntervalMs int `mapstructure:"video_poll_interval_ms"`
446
+ // VideoPollMaxAttempts 视频异步结果最大轮询次数。
447
+ VideoPollMaxAttempts int `mapstructure:"video_poll_max_attempts"`
448
+ }
449
+
450
+ // KafkaConfig Kafka 配置
451
+ type KafkaConfig struct {
452
+ Brokers string `mapstructure:"brokers"`
453
+ ClientID string `mapstructure:"client_id"`
454
+ GroupID string `mapstructure:"group_id"`
455
+ ConnectionTimeout int `mapstructure:"connection_timeout"`
456
+ AuthenticationTimeout int `mapstructure:"authentication_timeout"`
457
+ RequestTimeout int `mapstructure:"request_timeout"`
458
+ SASLUsername string `mapstructure:"sasl_username"`
459
+ SASLPassword string `mapstructure:"sasl_password"`
460
+ SASLMechanism string `mapstructure:"sasl_mechanism"`
461
+ SSLEnabled bool `mapstructure:"ssl_enabled"`
462
+ }
463
+
464
+ // LogConfig 日志配置
465
+ type LogConfig struct {
466
+ Level string `mapstructure:"level"`
467
+ Env string `mapstructure:"env"`
468
+ RequestSuccessLevel string `mapstructure:"request_success_level"`
469
+ RequestSuccessSampleRate float64 `mapstructure:"request_success_sample_rate"`
470
+ RequestSlowThreshold time.Duration `mapstructure:"request_slow_threshold"`
471
+ }
472
+
473
+ // AuditConfig 审计与访问日志治理配置
474
+ type AuditConfig struct {
475
+ AccessLog AccessLogConfig `mapstructure:"access_log"`
476
+ }
477
+
478
+ // AccessLogConfig 控制高频 HTTP 访问日志的保留策略
479
+ type AccessLogConfig struct {
480
+ Enabled bool `mapstructure:"enabled"`
481
+ SuccessReadSampleRate float64 `mapstructure:"success_read_sample_rate"`
482
+ SlowRequestThreshold time.Duration `mapstructure:"slow_request_threshold"`
483
+ RecordWriteMethods bool `mapstructure:"record_write_methods"`
484
+ RecordClientErrors bool `mapstructure:"record_client_errors"`
485
+ }
486
+
487
+ // ThrottleConfig 限流配置
488
+ type ThrottleConfig struct {
489
+ TTL int `mapstructure:"ttl"`
490
+ Limit int `mapstructure:"limit"`
491
+ }
492
+
493
+ // TracingConfig 分布式追踪配置
494
+ type TracingConfig struct {
495
+ // 是否启用追踪
496
+ Enabled bool `mapstructure:"enabled"`
497
+ // OTLP 采集端点(host:port 格式)
498
+ Endpoint string `mapstructure:"endpoint"`
499
+ // 服务名称
500
+ ServiceName string `mapstructure:"service_name"`
501
+ // 服务版本
502
+ ServiceVersion string `mapstructure:"service_version"`
503
+ // 运行环境
504
+ Environment string `mapstructure:"environment"`
505
+ // 采样率(0.0 ~ 1.0)
506
+ SamplingRatio float64 `mapstructure:"sampling_ratio"`
507
+ }
508
+
509
+ // ExportConfig 数据导出配置
510
+ // 存储凭证复用平台存储模块(p_config_storage_configs),不再独立配置 S3
511
+ type ExportConfig struct {
512
+ // 本地文件存储目录(开发环境无平台存储配置时使用)
513
+ LocalDir string `mapstructure:"local_dir"`
514
+ // 导出文件在存储桶内的路径前缀(默认 "exports")
515
+ PathPrefix string `mapstructure:"path_prefix"`
516
+ }
517
+
518
+ // ClamAVConfig ClamAV 病毒扫描配置
519
+ type ClamAVConfig struct {
520
+ // 是否启用病毒扫描
521
+ Enabled bool `mapstructure:"enabled"`
522
+ // clamd 守护进程地址,格式: tcp://host:port
523
+ Address string `mapstructure:"address"`
524
+ // 扫描超时时间(秒)
525
+ Timeout int `mapstructure:"timeout"`
526
+ }
527
+
528
+ // StorageConfig 存储资产管理配置
529
+ type StorageConfig struct {
530
+ // 软删除资产保留天数,超过后由清理任务物理删除并清理 OSS 文件
531
+ RetentionDays int `mapstructure:"retention_days"`
532
+ }
533
+
534
+ // DocumentationConfig 定义文档编译和隐私指纹的进程边界,只保存密钥引用。
535
+ type DocumentationConfig struct {
536
+ NodeExecutable string `mapstructure:"node_executable"`
537
+ SiteRoot string `mapstructure:"site_root"`
538
+ CompilerScript string `mapstructure:"compiler_script"`
539
+ BuildTimeout time.Duration `mapstructure:"build_timeout"`
540
+ // PrivacyHMACKeyRef 指向跨 platform-api 与 documentation-api 共享的 32 字节以上伪名化密钥。
541
+ PrivacyHMACKeyRef string `mapstructure:"privacy_hmac_key_ref"`
542
+ DomainBindingProviderKey string `mapstructure:"domain_binding_provider_key"`
543
+ DomainGatewayAdminURL string `mapstructure:"domain_gateway_admin_url"`
544
+ DomainGatewayAdminKeyRef string `mapstructure:"domain_gateway_admin_key_ref"`
545
+ DomainOriginUpstreamID string `mapstructure:"domain_origin_upstream_id"`
546
+ DomainCertificateRoot string `mapstructure:"domain_certificate_root"`
547
+ DomainRoutePriority int `mapstructure:"domain_route_priority"`
548
+ }
549
+
550
+ // APIContractConfig 定义 API 契约平台的进程级运行时边界。
551
+ type APIContractConfig struct {
552
+ CompatibilityEvaluator APIContractCompatibilityEvaluatorConfig `mapstructure:"compatibility_evaluator"`
553
+ }
554
+
555
+ // APIContractCompatibilityEvaluatorConfig 定义兼容性评估引擎的受控执行配置。
556
+ type APIContractCompatibilityEvaluatorConfig struct {
557
+ Executable string `mapstructure:"executable"`
558
+ Timeout time.Duration `mapstructure:"timeout"`
559
+ }
560
+
561
+ // SentryConfig Sentry 错误追踪配置
562
+ type SentryConfig struct {
563
+ // 是否启用 Sentry
564
+ Enabled bool `mapstructure:"enabled"`
565
+ // Sentry DSN
566
+ DSN string `mapstructure:"dsn"`
567
+ // 运行环境(development / staging / production)
568
+ Environment string `mapstructure:"environment"`
569
+ // 发布版本
570
+ Release string `mapstructure:"release"`
571
+ // 事件采样率(0.0 ~ 1.0)
572
+ SampleRate float64 `mapstructure:"sample_rate"`
573
+ // 事务追踪采样率(0.0 ~ 1.0)
574
+ TracesSampleRate float64 `mapstructure:"traces_sample_rate"`
575
+ // 是否启用调试模式
576
+ Debug bool `mapstructure:"debug"`
577
+ }
578
+
579
+ // GeoIPConfig GeoIP 地理定位配置
580
+ type GeoIPConfig struct {
581
+ // MaxMind GeoLite2 数据库文件路径(为空则禁用地理定位)
582
+ DatabasePath string `mapstructure:"database_path"`
583
+ }
584
+
585
+ // TemporalConfig Temporal 工作流引擎配置
586
+ type TemporalConfig struct {
587
+ // 是否启用 Temporal Worker(false 则不启动 Worker)
588
+ Enabled bool `mapstructure:"enabled"`
589
+ // Temporal Server 地址(如 localhost:7233)
590
+ HostPort string `mapstructure:"host_port"`
591
+ // Temporal Namespace(启用时必填)
592
+ Namespace string `mapstructure:"namespace"`
593
+ // TLS 连接配置
594
+ TLS TemporalTLSConfig `mapstructure:"tls"`
595
+ // Worker 执行限流配置
596
+ Worker TemporalWorkerConfig `mapstructure:"worker"`
597
+ // ProviderPollWorker Provider 轮询 Worker 执行限流配置
598
+ ProviderPollWorker TemporalWorkerConfig `mapstructure:"provider_poll_worker"`
599
+ }
600
+
601
+ // TemporalWorkerConfig Temporal Worker 执行限流配置
602
+ type TemporalWorkerConfig struct {
603
+ // 单进程最大并发 Activity 数,避免媒体转码/上传类 activity 抢占所有 CPU。
604
+ MaxConcurrentActivities int `mapstructure:"max_concurrent_activities"`
605
+ // 单进程最大并发 Workflow Task 数,不能配置为 1。
606
+ MaxConcurrentWorkflowTasks int `mapstructure:"max_concurrent_workflow_tasks"`
607
+ // Activity poller 数量。
608
+ MaxConcurrentActivityTaskPollers int `mapstructure:"max_concurrent_activity_task_pollers"`
609
+ // Workflow Task poller 数量,不能配置为 1。
610
+ MaxConcurrentWorkflowTaskPollers int `mapstructure:"max_concurrent_workflow_task_pollers"`
611
+ }
612
+
613
+ // TemporalTLSConfig Temporal TLS 连接配置
614
+ type TemporalTLSConfig struct {
615
+ // 是否启用 TLS / mTLS
616
+ Enabled bool `mapstructure:"enabled"`
617
+ // 覆盖 TLS ServerName(SNI);为空时由 HostPort 推导
618
+ ServerName string `mapstructure:"server_name"`
619
+ // 是否跳过主机名校验(仅限受控内网/自签场景)
620
+ DisableHostVerification bool `mapstructure:"disable_host_verification"`
621
+ // 自定义 CA 证书路径(可选)
622
+ CAPath string `mapstructure:"ca_path"`
623
+ // 客户端证书路径(mTLS 可选)
624
+ CertPath string `mapstructure:"cert_path"`
625
+ // 客户端私钥路径(mTLS 可选)
626
+ KeyPath string `mapstructure:"key_path"`
627
+ }
628
+
629
+ // AIConfig AI 模块数据源配置
630
+ type AIConfig struct {
631
+ // Products 按产品配置数据源
632
+ Products map[string]AIProductDataSource `mapstructure:"products"`
633
+ }
634
+
635
+ // AIProductDataSource 单个产品的数据源配置
636
+ type AIProductDataSource struct {
637
+ // DB 数据库数据源(可选)
638
+ DB *AIDBSourceConfig `mapstructure:"db"`
639
+ // Logs 日志数据源列表
640
+ Logs []AILogSourceConfig `mapstructure:"logs"`
641
+ }
642
+
643
+ // AIDBSourceConfig 数据库数据源配置
644
+ type AIDBSourceConfig struct {
645
+ // Driver 数据库驱动:postgres(复用主库连接)或 mysql(独立连接)
646
+ Driver string `mapstructure:"driver"`
647
+ Host string `mapstructure:"host"`
648
+ Port int `mapstructure:"port"`
649
+ Username string `mapstructure:"username"`
650
+ Password string `mapstructure:"password"`
651
+ Database string `mapstructure:"database"`
652
+ }
653
+
654
+ // AILogSourceConfig 日志数据源配置
655
+ type AILogSourceConfig struct {
656
+ // Type 日志类型:file(磁盘 JSON 日志)、zap(进程内 zap 日志)、loki(远程 Loki API)
657
+ Type string `mapstructure:"type"`
658
+ // Service 服务名标识
659
+ Service string `mapstructure:"service"`
660
+ // Dir 日志文件目录(type=file 时使用)
661
+ Dir string `mapstructure:"dir"`
662
+ // Endpoint Loki HTTP 端点(type=loki 时使用),如 http://loki:3100
663
+ Endpoint string `mapstructure:"endpoint"`
664
+ // Labels Loki 标签选择器(type=loki 时使用),如 {service="data-service"}
665
+ Labels string `mapstructure:"labels"`
666
+ }
667
+
668
+ // IsDevelopment 是否开发环境
669
+ func (c *Config) IsDevelopment() bool {
670
+ return c.App.Env == "development"
671
+ }
672
+
673
+ // IsProduction 是否生产环境
674
+ func (c *Config) IsProduction() bool {
675
+ return c.App.Env == "production"
676
+ }
677
+
678
+ // IsIdentityAccessTokenIssuer 标识当前进程是否承担用户访问令牌签发职责。
679
+ func (c *Config) IsIdentityAccessTokenIssuer() bool {
680
+ return strings.TrimSpace(c.Identity.AccessToken.PrivateKeyPath) != ""
681
+ }
682
+
683
+ // IsIAMServiceTokenIssuer 标识当前进程是否承担服务令牌签发职责。
684
+ func (c *Config) IsIAMServiceTokenIssuer() bool {
685
+ return strings.TrimSpace(c.InternalService.ServiceTokenSigning.PrivateKeyPath) != ""
686
+ }
687
+
688
+ // Load 加载配置,优先级:环境变量 > config.{env}.yaml > config.yaml > 代码默认值
689
+ func Load() (*Config, error) {
690
+ cfg, err := loadConfig()
691
+ if err != nil {
692
+ return nil, err
693
+ }
694
+ if err := validate(cfg); err != nil {
695
+ return nil, err
696
+ }
697
+ return cfg, nil
698
+ }
699
+
700
+ // LoadDatabaseCommandConfig 加载只读数据库命令所需配置,不要求无关运行时密钥。
701
+ func LoadDatabaseCommandConfig() (*DatabaseCommandConfig, error) {
702
+ cfg, err := loadConfig()
703
+ if err != nil {
704
+ return nil, err
705
+ }
706
+ if err := validateDatabaseCommandConfig(cfg.Database); err != nil {
707
+ return nil, err
708
+ }
709
+ return &DatabaseCommandConfig{Database: cfg.Database, Log: cfg.Log}, nil
710
+ }
711
+
712
+ // LoadBootstrapCommandConfig 加载配置初始化命令所需配置,不要求无关服务运行时密钥。
713
+ func LoadBootstrapCommandConfig() (*BootstrapCommandConfig, error) {
714
+ cfg, err := loadConfig()
715
+ if err != nil {
716
+ return nil, err
717
+ }
718
+ if err := validateDatabaseCommandConfig(cfg.Database); err != nil {
719
+ return nil, err
720
+ }
721
+ return &BootstrapCommandConfig{
722
+ Database: cfg.Database,
723
+ Log: cfg.Log,
724
+ EncryptionKey: cfg.EncryptionKey,
725
+ }, nil
726
+ }
727
+
728
+ // LoadSecretCutoverCommandConfig 加载长期凭证切换所需配置,不要求无关服务运行时密钥。
729
+ func LoadSecretCutoverCommandConfig() (*SecretCutoverCommandConfig, error) {
730
+ cfg, err := loadConfig()
731
+ if err != nil {
732
+ return nil, err
733
+ }
734
+ if strings.TrimSpace(cfg.EncryptionKey) == "" {
735
+ return nil, fmt.Errorf("secret cutover requires ENCRYPTION_KEY")
736
+ }
737
+ if strings.TrimSpace(cfg.EncryptionKeyID) == "" {
738
+ return nil, fmt.Errorf("secret cutover requires ENCRYPTION_KEY_ID")
739
+ }
740
+ if strings.TrimSpace(cfg.Secret.ActiveDriverCode) == "" || strings.TrimSpace(cfg.Secret.Envelope.DriverCode) == "" {
741
+ return nil, fmt.Errorf("secret cutover requires SECRET_ACTIVE_DRIVER_CODE and SECRET_ENVELOPE_DRIVER_CODE")
742
+ }
743
+ if cfg.Secret.Envelope.MaximumPlaintextBytes <= 0 || cfg.Secret.DriverHealthTimeout <= 0 {
744
+ return nil, fmt.Errorf("secret cutover driver limits must be positive")
745
+ }
746
+ return &SecretCutoverCommandConfig{
747
+ EncryptionKey: cfg.EncryptionKey,
748
+ EncryptionKeyID: cfg.EncryptionKeyID,
749
+ EncryptionKeyringFile: cfg.EncryptionKeyringFile,
750
+ Secret: cfg.Secret,
751
+ }, nil
752
+ }
753
+
754
+ func validateDatabaseCommandConfig(cfg DatabaseConfig) error {
755
+ if strings.TrimSpace(cfg.Host) == "" || cfg.Port <= 0 ||
756
+ strings.TrimSpace(cfg.Username) == "" || strings.TrimSpace(cfg.Name) == "" {
757
+ return fmt.Errorf("database command requires host, port, username, and database name")
758
+ }
759
+ return nil
760
+ }
761
+
762
+ func loadConfig() (*Config, error) {
763
+ v := viper.New()
764
+ if err := ValidateDocumentationEnvironmentVariables(documentationEnvironmentVariables); err != nil {
765
+ return nil, fmt.Errorf("validate documentation environment metadata: %w", err)
766
+ }
767
+
768
+ // 1. 代码默认值(最低优先级)
769
+ setDefaults(v)
770
+
771
+ // 2. 本地 dotenv(仅补充运行时环境变量,不覆盖显式导出的系统环境变量)
772
+ loadLocalEnvFiles()
773
+
774
+ // 3. 基础配置文件
775
+ v.SetConfigType("yaml")
776
+ v.AddConfigPath("./configs")
777
+ v.AddConfigPath(".")
778
+
779
+ v.SetConfigName("config")
780
+ _ = v.ReadInConfig() //nolint:errcheck // viper 配置加载,文件不存在时静默降级
781
+
782
+ // 4. 环境配置文件(覆盖基础配置)
783
+ // S4 例外: 引导阶段 Config 尚未创建,此处直接读取环境变量
784
+ env := os.Getenv("APP_ENV")
785
+ if env == "" {
786
+ env = "local"
787
+ }
788
+ v.SetConfigName("config." + env)
789
+ _ = v.MergeInConfig() //nolint:errcheck // viper 配置加载,文件不存在时静默降级
790
+
791
+ // 5. 环境变量(最高优先级)
792
+ v.AutomaticEnv()
793
+ if err := bindEnvVars(v); err != nil {
794
+ return nil, err
795
+ }
796
+ if err := applyInternalServiceOutboundIdentitiesEnvironment(v); err != nil {
797
+ return nil, err
798
+ }
799
+ if err := applyJSONRegistryEnvironment[IdentityProtectedResourceConfig](v, "ONEX_IDENTITY_PROTECTED_RESOURCES", "identity.access_token.protected_resources"); err != nil {
800
+ return nil, err
801
+ }
802
+ if err := applyJSONRegistryEnvironment[IdentityClientConfig](v, "ONEX_IDENTITY_CLIENTS", "identity.clients"); err != nil {
803
+ return nil, err
804
+ }
805
+ if err := applyProductAdminTargetsEnvironment(v); err != nil {
806
+ return nil, err
807
+ }
808
+
809
+ // 解析到 struct
810
+ cfg := &Config{}
811
+ if err := v.Unmarshal(cfg); err != nil {
812
+ return nil, fmt.Errorf("unmarshal config: %w", err)
813
+ }
814
+
815
+ // 用 APP_ENV 覆盖 app.env(确保一致性)
816
+ if cfg.App.Env == "" || cfg.App.Env == "development" {
817
+ cfg.App.Env = env
818
+ }
819
+
820
+ return cfg, nil
821
+ }
822
+
823
+ func applyInternalServiceOutboundIdentitiesEnvironment(v *viper.Viper) error {
824
+ raw := strings.TrimSpace(os.Getenv("ONEX_INTERNAL_SERVICE_OUTBOUND_IDENTITIES"))
825
+ if raw == "" {
826
+ return nil
827
+ }
828
+ identities := make(map[string]InternalServiceIdentityConfig)
829
+ decoder := json.NewDecoder(strings.NewReader(raw))
830
+ decoder.DisallowUnknownFields()
831
+ if err := decoder.Decode(&identities); err != nil {
832
+ return fmt.Errorf("decode ONEX_INTERNAL_SERVICE_OUTBOUND_IDENTITIES: %w", err)
833
+ }
834
+ var trailing any
835
+ if err := decoder.Decode(&trailing); err != io.EOF {
836
+ return fmt.Errorf("decode ONEX_INTERNAL_SERVICE_OUTBOUND_IDENTITIES: trailing JSON value is not allowed")
837
+ }
838
+ v.Set("internal_service.outbound_identities", identities)
839
+ return nil
840
+ }
841
+
842
+ func applyJSONRegistryEnvironment[T any](v *viper.Viper, environmentKey, configKey string) error {
843
+ raw, exists := os.LookupEnv(environmentKey)
844
+ if !exists {
845
+ return nil
846
+ }
847
+ registry := make(map[string]T)
848
+ decoder := json.NewDecoder(strings.NewReader(raw))
849
+ decoder.DisallowUnknownFields()
850
+ if err := decoder.Decode(&registry); err != nil {
851
+ return fmt.Errorf("decode %s: %w", environmentKey, err)
852
+ }
853
+ if registry == nil {
854
+ return fmt.Errorf("decode %s: registry must be a JSON object", environmentKey)
855
+ }
856
+ var trailing any
857
+ if err := decoder.Decode(&trailing); err != io.EOF {
858
+ return fmt.Errorf("decode %s: trailing JSON value is not allowed", environmentKey)
859
+ }
860
+ v.Set(configKey, registry)
861
+ return nil
862
+ }
863
+
864
+ func applyProductAdminTargetsEnvironment(v *viper.Viper) error {
865
+ raw, exists := os.LookupEnv("PRODUCT_ADMIN_TARGETS")
866
+ if !exists {
867
+ return nil
868
+ }
869
+ canonical, err := canonicaljson.Canonicalize(json.RawMessage(raw))
870
+ if err != nil {
871
+ return fmt.Errorf("decode PRODUCT_ADMIN_TARGETS: %w", err)
872
+ }
873
+
874
+ environmentTargets := make(map[string]*productAdminTargetEnvironmentConfig)
875
+ decoder := json.NewDecoder(strings.NewReader(string(canonical)))
876
+ decoder.DisallowUnknownFields()
877
+ if err := decoder.Decode(&environmentTargets); err != nil {
878
+ return fmt.Errorf("decode PRODUCT_ADMIN_TARGETS: %w", err)
879
+ }
880
+ if environmentTargets == nil {
881
+ return fmt.Errorf("decode PRODUCT_ADMIN_TARGETS: registry must be a JSON object")
882
+ }
883
+
884
+ targets := make(map[string]ProductAdminTargetConfig, len(environmentTargets))
885
+ for productCode, environmentTarget := range environmentTargets {
886
+ if environmentTarget == nil {
887
+ return fmt.Errorf("decode PRODUCT_ADMIN_TARGETS: target %q must be a JSON object", productCode)
888
+ }
889
+ requestTimeout, parseErr := time.ParseDuration(environmentTarget.RequestTimeout)
890
+ if parseErr != nil {
891
+ return fmt.Errorf("decode PRODUCT_ADMIN_TARGETS: target %q request_timeout: %w", productCode, parseErr)
892
+ }
893
+ targets[productCode] = ProductAdminTargetConfig{
894
+ AllowedOrigin: environmentTarget.AllowedOrigin,
895
+ BaseURL: environmentTarget.BaseURL,
896
+ RequestTimeout: requestTimeout,
897
+ ServiceIdentityName: environmentTarget.ServiceIdentityName,
898
+ SignatureKeyID: environmentTarget.SignatureKeyID,
899
+ SignatureSecretRef: environmentTarget.SignatureSecretRef,
900
+ }
901
+ }
902
+ v.Set("product_admin.targets", targets)
903
+ return nil
904
+ }
905
+
906
+ func loadLocalEnvFiles() {
907
+ baseFiles := []string{
908
+ ".env",
909
+ ".env.local",
910
+ }
911
+
912
+ for _, path := range baseFiles {
913
+ loadEnvFile(path)
914
+ }
915
+
916
+ env := os.Getenv("APP_ENV")
917
+ if env == "" {
918
+ env = "local"
919
+ }
920
+
921
+ envFiles := []string{
922
+ ".env." + env,
923
+ ".env." + env + ".local",
924
+ }
925
+
926
+ switch env {
927
+ case "local", "development", "dev":
928
+ envFiles = append(envFiles, ".env.dev")
929
+ }
930
+
931
+ for _, path := range envFiles {
932
+ loadEnvFile(path)
933
+ }
934
+ }
935
+
936
+ func loadEnvFile(path string) {
937
+ if _, err := os.Stat(path); err != nil {
938
+ return
939
+ }
940
+ _ = gotenv.Load(path) //nolint:errcheck // 本地 dotenv 缺失或格式异常不应阻断启动
941
+ }
942
+
943
+ func setDefaults(v *viper.Viper) {
944
+ // 应用
945
+ v.SetDefault("app.name", "ai-chat-platform")
946
+ v.SetDefault("app.port", 3000)
947
+ v.SetDefault("app.host", "0.0.0.0")
948
+ v.SetDefault("app.env", "development")
949
+ v.SetDefault("app.trusted_proxies", []string{"127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"})
950
+
951
+ // 数据库
952
+ v.SetDefault("database.host", "localhost")
953
+ v.SetDefault("database.port", 5432)
954
+ v.SetDefault("database.username", "postgres")
955
+ v.SetDefault("database.password", "")
956
+ v.SetDefault("database.name", "ai_chat_platform")
957
+ v.SetDefault("database.ssl", false)
958
+ v.SetDefault("database.logging", false)
959
+ v.SetDefault("database.max_open_conns", 25)
960
+ v.SetDefault("database.max_idle_conns", 10)
961
+ v.SetDefault("database.conn_max_lifetime", "5m")
962
+ v.SetDefault("database.conn_max_idle_time", "5m")
963
+
964
+ // ClickHouse(Insights 列存仓库 — 默认 disabled,CI/本地无需启动)
965
+ v.SetDefault("clickhouse.enabled", false)
966
+ v.SetDefault("clickhouse.host", "localhost")
967
+ v.SetDefault("clickhouse.port", 9000)
968
+ v.SetDefault("clickhouse.database", "insights")
969
+ v.SetDefault("clickhouse.username", "default")
970
+ v.SetDefault("clickhouse.password", "")
971
+ v.SetDefault("clickhouse.dial_timeout", "10s")
972
+ v.SetDefault("clickhouse.read_timeout", "30s")
973
+ v.SetDefault("clickhouse.max_open_conns", 20)
974
+ v.SetDefault("clickhouse.max_idle_conns", 5)
975
+ v.SetDefault("clickhouse.conn_max_lifetime", "1h")
976
+ v.SetDefault("clickhouse.compression", true)
977
+ v.SetDefault("clickhouse.secure", false)
978
+
979
+ // Redis
980
+ v.SetDefault("redis.host", "localhost")
981
+ v.SetDefault("redis.port", 6379)
982
+ v.SetDefault("redis.password", "")
983
+ v.SetDefault("redis.db", 0)
984
+
985
+ // OpenAI
986
+ v.SetDefault("openai.api_key", "")
987
+ v.SetDefault("openai.model", "gpt-4")
988
+ v.SetDefault("openai.base_url", "https://api.openai.com/v1")
989
+
990
+ // Anthropic
991
+ v.SetDefault("anthropic.api_key", "")
992
+ v.SetDefault("anthropic.model", "claude-3-opus-20240229")
993
+
994
+ // Kafka
995
+ v.SetDefault("kafka.brokers", "localhost:9092")
996
+ v.SetDefault("kafka.client_id", "ai-chat-platform")
997
+ v.SetDefault("kafka.group_id", "ai-chat-api-consumer")
998
+ v.SetDefault("kafka.connection_timeout", 10000)
999
+ v.SetDefault("kafka.authentication_timeout", 10000)
1000
+ v.SetDefault("kafka.request_timeout", 30000)
1001
+ v.SetDefault("kafka.sasl_mechanism", "plain")
1002
+ v.SetDefault("kafka.ssl_enabled", false)
1003
+
1004
+ // 日志
1005
+ v.SetDefault("log.level", "debug")
1006
+ v.SetDefault("log.env", "development")
1007
+ v.SetDefault("log.request_success_level", "debug")
1008
+ v.SetDefault("log.request_success_sample_rate", 0.0)
1009
+ v.SetDefault("log.request_slow_threshold", "2s")
1010
+
1011
+ // 审计与访问日志治理
1012
+ v.SetDefault("audit.access_log.enabled", true)
1013
+ v.SetDefault("audit.access_log.success_read_sample_rate", 0.05)
1014
+ v.SetDefault("audit.access_log.slow_request_threshold", "2s")
1015
+ v.SetDefault("audit.access_log.record_write_methods", true)
1016
+ v.SetDefault("audit.access_log.record_client_errors", true)
1017
+
1018
+ // 限流
1019
+ v.SetDefault("throttle.ttl", 60)
1020
+ v.SetDefault("throttle.limit", 100)
1021
+
1022
+ // 导出
1023
+ v.SetDefault("export.local_dir", "/tmp/exports")
1024
+ v.SetDefault("export.path_prefix", "exports")
1025
+
1026
+ // 追踪
1027
+ v.SetDefault("tracing.enabled", false)
1028
+ v.SetDefault("tracing.endpoint", "localhost:4318")
1029
+ v.SetDefault("tracing.service_name", "onex-backend")
1030
+ v.SetDefault("tracing.service_version", "1.0.0")
1031
+ v.SetDefault("tracing.environment", "development")
1032
+ v.SetDefault("tracing.sampling_ratio", 1.0)
1033
+
1034
+ // Sentry
1035
+ v.SetDefault("sentry.enabled", false)
1036
+ v.SetDefault("sentry.dsn", "")
1037
+ v.SetDefault("sentry.environment", "development")
1038
+ v.SetDefault("sentry.release", "")
1039
+ v.SetDefault("sentry.sample_rate", 1.0)
1040
+ v.SetDefault("sentry.traces_sample_rate", 0.1)
1041
+ v.SetDefault("sentry.debug", false)
1042
+
1043
+ // ClamAV
1044
+ v.SetDefault("clamav.enabled", false)
1045
+ v.SetDefault("clamav.address", "tcp://localhost:3310")
1046
+ v.SetDefault("clamav.timeout", 30)
1047
+
1048
+ // 存储
1049
+ v.SetDefault("storage.retention_days", 30)
1050
+
1051
+ applyDocumentationDefaults(v)
1052
+
1053
+ // GeoIP
1054
+ v.SetDefault("geoip.database_path", "data/geoip/GeoLite2-City.mmdb")
1055
+
1056
+ // ExchangeRate-API
1057
+ v.SetDefault("exchange_rate_api_key", "")
1058
+
1059
+ // 内部服务调用。
1060
+ v.SetDefault("internal_service.platform_base_url", "http://platform-api:3002/internal/v1")
1061
+ v.SetDefault("integration_event.inbound.trust_registry_file", "configs/trust/integration-event-inbound.json")
1062
+ v.SetDefault("integration_event.inbound.runtime_artifact_file", "api/openapi/services/platform-api/dist/runtime-contract.json")
1063
+ v.SetDefault("integration_event.inbound.release_manifest_file", "api/openapi/dist/release-manifest.json")
1064
+ v.SetDefault("integration_event.inbound.migration_marker_file", "migrations/000001_platform_baseline.up.sql")
1065
+ v.SetDefault("internal_service.service_token_signing.key_id", "iam-service-local-2026-08")
1066
+ v.SetDefault("internal_service.service_token_signing.private_key_path", "")
1067
+ v.SetDefault("internal_service.service_token_signing.public_key_directory", "")
1068
+ v.SetDefault("internal_service.outbound_identities", map[string]InternalServiceIdentityConfig{})
1069
+ v.SetDefault("internal_service.token_ttl_seconds", defaultInternalServiceTokenTTLSeconds)
1070
+ v.SetDefault("internal_service.authorization_consistency_token_ttl_seconds", defaultIAMConsistencyTokenTTLSeconds)
1071
+ v.SetDefault("internal_service.request_timeout", "10s")
1072
+ v.SetDefault("admin_experience.external_products", []AdminExperienceExternalProductConfig{})
1073
+ v.SetDefault("product_admin.targets", map[string]ProductAdminTargetConfig{})
1074
+
1075
+ // 内容安全 provider 默认关闭,避免仅接入代码就产生外部调用和计费。
1076
+ v.SetDefault("content_safety.aliyun.enabled", false)
1077
+ v.SetDefault("content_safety.aliyun.provider_code", "aliyun_content_safety")
1078
+ v.SetDefault("content_safety.aliyun.region_id", "ap-southeast-1")
1079
+ v.SetDefault("content_safety.aliyun.endpoint", "green-cip.ap-southeast-1.aliyuncs.com")
1080
+ v.SetDefault("content_safety.aliyun.text_service", "query_security_check_intl")
1081
+ v.SetDefault("content_safety.aliyun.image_service", "postImageCheckByVL_global")
1082
+ v.SetDefault("content_safety.aliyun.video_service", "videoDetection_global")
1083
+ v.SetDefault("content_safety.aliyun.connect_timeout_ms", 3000)
1084
+ v.SetDefault("content_safety.aliyun.read_timeout_ms", 10000)
1085
+ v.SetDefault("content_safety.aliyun.video_poll_initial_delay_ms", 30000)
1086
+ v.SetDefault("content_safety.aliyun.video_poll_interval_ms", 30000)
1087
+ v.SetDefault("content_safety.aliyun.video_poll_max_attempts", 10)
1088
+
1089
+ // 通信信誉检测默认使用 DNSBL;生产可替换为私有 DQS/商业区。
1090
+ v.SetDefault("communication.reputation.ip_blocklist_zones", []string{"zen.spamhaus.org"})
1091
+ v.SetDefault("communication.reputation.domain_blocklist_zones", []string{"dbl.spamhaus.org"})
1092
+ v.SetDefault("communication.reputation.dns_lookup_timeout_seconds", 5)
1093
+ v.SetDefault("communication.reputation.recheck_interval_hours", 24)
1094
+ v.SetDefault("communication.reputation.recheck_batch_limit", 50)
1095
+ v.SetDefault("communication.unsubscribe_signing_secret", "")
1096
+
1097
+ // 用户访问令牌采用单资源 RFC 9068 profile;私钥路径与 HMAC 密钥由部署环境注入。
1098
+ v.SetDefault("identity.oauth_state_signing_secret", "")
1099
+ v.SetDefault("identity.clients", map[string]map[string]any{
1100
+ "admin-web": {
1101
+ "product_code": "platform",
1102
+ "application_code": "admin_web",
1103
+ "allowed_principal_types": []string{"user"},
1104
+ "registration_source": "admin",
1105
+ "token_exchange_service_accounts": []string{},
1106
+ "token_exchange_resources": []string{},
1107
+ },
1108
+ })
1109
+ v.SetDefault("identity.access_token.issuer", "http://localhost:3002")
1110
+ v.SetDefault("identity.access_token.signing_key_id", "onex-local-2026-07")
1111
+ v.SetDefault("identity.access_token.private_key_path", "")
1112
+ v.SetDefault("identity.access_token.public_key_directory", "")
1113
+ v.SetDefault("identity.access_token.clock_skew_leeway", defaultIdentityClockSkewLeeway.String())
1114
+ v.SetDefault("identity.access_token.delegated_token_ttl", (time.Duration(defaultDelegatedAccessTokenTTLSeconds) * time.Second).String())
1115
+ v.SetDefault("identity.access_token.protected_resources", map[string]map[string]any{
1116
+ "platform_internal_api": {
1117
+ "resource_identifier": "http://localhost:3002",
1118
+ "audience": serviceidentity.AudiencePlatformInternalAPI,
1119
+ "resource_name": "OneX Platform Internal API",
1120
+ },
1121
+ "documentation_api": {
1122
+ "resource_identifier": "http://localhost:3010",
1123
+ "audience": serviceidentity.AudienceDocumentationAPI,
1124
+ "resource_name": "OneX Documentation API",
1125
+ },
1126
+ })
1127
+ v.SetDefault("identity.guest_capability.secret_provider_file", "")
1128
+ v.SetDefault("identity.guest_capability.initial_key_id", "")
1129
+ v.SetDefault("identity.guest_capability.verifier_instance_id", "")
1130
+ v.SetDefault("identity.access_token.resources", map[string]map[string]any{
1131
+ "platform": {
1132
+ "audience": "urn:onex:resource:onex-api",
1133
+ "user_scopes": []string{"email", "openid", "profile"},
1134
+ "guest_scopes": []string{"openid", "profile"},
1135
+ },
1136
+ })
1137
+
1138
+ // FeatureFlag 运行时快照必须在有界陈旧窗口内服务。
1139
+ v.SetDefault("feature_flag.freshness_ttl", "30s")
1140
+ v.SetDefault("feature_flag.maximum_stale_age", "15m")
1141
+ v.SetDefault("feature_flag.cache_ttl", "30m")
1142
+
1143
+ // 支付风控主体哈希密钥。
1144
+ v.SetDefault("payment.risk_subject_hash_key", "")
1145
+ v.SetDefault("payment.store_purchase_reference_hash_key", "")
1146
+
1147
+ // 客户在场结账安全配置由部署环境注入,缺失时对应服务拒绝启动。
1148
+ v.SetDefault("checkout.method_reference_signing_key", "")
1149
+ v.SetDefault("checkout.guest_actor_scope_signing_key", "")
1150
+ v.SetDefault("checkout.replay_encryption_key_version", "")
1151
+
1152
+ // SecretBroker 驱动与密钥必须由部署环境选择;只有非敏感运行边界提供默认值。
1153
+ v.SetDefault("secret.active_driver_code", "")
1154
+ v.SetDefault("secret.fingerprint_key", "")
1155
+ v.SetDefault("secret.driver_health_timeout", "5s")
1156
+ v.SetDefault("secret.envelope.driver_code", "")
1157
+ v.SetDefault("secret.envelope.maximum_plaintext_bytes", 256*1024)
1158
+ v.SetDefault("secret.runtime.service_account_code", "")
1159
+ v.SetDefault("secret.runtime.audience", "")
1160
+ v.SetDefault("secret.runtime.default_product_code", "platform")
1161
+ v.SetDefault("secret.runtime.lease_ttl", "30s")
1162
+ v.SetDefault("secret.retention.minimum_destruction_delay", "24h")
1163
+ v.SetDefault("secret.retention.access_event_purge_enabled", false)
1164
+ v.SetDefault("secret.retention.legal_hold_owner_resources", []string{})
1165
+ v.SetDefault("secret.worker.service_account_code", "")
1166
+ v.SetDefault("secret.worker.batch_size", 100)
1167
+ v.SetDefault("secret.worker.partition_months_ahead", 3)
1168
+
1169
+ // Temporal
1170
+ v.SetDefault("temporal.enabled", false)
1171
+ v.SetDefault("temporal.host_port", "")
1172
+ v.SetDefault("temporal.namespace", "")
1173
+ v.SetDefault("temporal.tls.enabled", false)
1174
+ v.SetDefault("temporal.tls.server_name", "")
1175
+ v.SetDefault("temporal.tls.disable_host_verification", false)
1176
+ v.SetDefault("temporal.tls.ca_path", "")
1177
+ v.SetDefault("temporal.tls.cert_path", "")
1178
+ v.SetDefault("temporal.tls.key_path", "")
1179
+ v.SetDefault("temporal.worker.max_concurrent_activities", 4)
1180
+ v.SetDefault("temporal.worker.max_concurrent_workflow_tasks", 16)
1181
+ v.SetDefault("temporal.worker.max_concurrent_activity_task_pollers", 2)
1182
+ v.SetDefault("temporal.worker.max_concurrent_workflow_task_pollers", 2)
1183
+ }
1184
+
1185
+ //nolint:errcheck // 既有静态绑定保持当前契约;注册表新增绑定显式传播错误。
1186
+ func bindEnvVars(v *viper.Viper) error {
1187
+ _ = v.BindEnv("app.env", "NODE_ENV")
1188
+ _ = v.BindEnv("app.name", "APP_NAME")
1189
+ _ = v.BindEnv("app.port", "APP_PORT")
1190
+ _ = v.BindEnv("app.host", "APP_HOST")
1191
+ _ = v.BindEnv("app.trusted_proxies", "TRUSTED_PROXIES")
1192
+
1193
+ _ = v.BindEnv("database.host", "DATABASE_HOST")
1194
+ _ = v.BindEnv("database.port", "DATABASE_PORT")
1195
+ _ = v.BindEnv("database.username", "DATABASE_USERNAME")
1196
+ _ = v.BindEnv("database.password", "DATABASE_PASSWORD")
1197
+ _ = v.BindEnv("database.name", "DATABASE_NAME")
1198
+ _ = v.BindEnv("database.ssl", "DATABASE_SSL")
1199
+ _ = v.BindEnv("database.logging", "DATABASE_LOGGING")
1200
+ _ = v.BindEnv("database.max_open_conns", "DATABASE_MAX_OPEN_CONNS")
1201
+ _ = v.BindEnv("database.max_idle_conns", "DATABASE_MAX_IDLE_CONNS")
1202
+ _ = v.BindEnv("database.conn_max_lifetime", "DATABASE_CONN_MAX_LIFETIME")
1203
+ _ = v.BindEnv("database.conn_max_idle_time", "DATABASE_CONN_MAX_IDLE_TIME")
1204
+
1205
+ _ = v.BindEnv("clickhouse.enabled", "CLICKHOUSE_ENABLED")
1206
+ _ = v.BindEnv("clickhouse.host", "CLICKHOUSE_HOST")
1207
+ _ = v.BindEnv("clickhouse.port", "CLICKHOUSE_PORT")
1208
+ _ = v.BindEnv("clickhouse.database", "CLICKHOUSE_DATABASE")
1209
+ _ = v.BindEnv("clickhouse.username", "CLICKHOUSE_USERNAME")
1210
+ _ = v.BindEnv("clickhouse.password", "CLICKHOUSE_PASSWORD")
1211
+ _ = v.BindEnv("clickhouse.dial_timeout", "CLICKHOUSE_DIAL_TIMEOUT")
1212
+ _ = v.BindEnv("clickhouse.read_timeout", "CLICKHOUSE_READ_TIMEOUT")
1213
+ _ = v.BindEnv("clickhouse.max_open_conns", "CLICKHOUSE_MAX_OPEN_CONNS")
1214
+ _ = v.BindEnv("clickhouse.max_idle_conns", "CLICKHOUSE_MAX_IDLE_CONNS")
1215
+ _ = v.BindEnv("clickhouse.conn_max_lifetime", "CLICKHOUSE_CONN_MAX_LIFETIME")
1216
+ _ = v.BindEnv("clickhouse.compression", "CLICKHOUSE_COMPRESSION")
1217
+ _ = v.BindEnv("clickhouse.secure", "CLICKHOUSE_SECURE")
1218
+
1219
+ _ = v.BindEnv("redis.host", "REDIS_HOST")
1220
+ _ = v.BindEnv("redis.port", "REDIS_PORT")
1221
+ _ = v.BindEnv("redis.password", "REDIS_PASSWORD")
1222
+ _ = v.BindEnv("redis.db", "REDIS_DB")
1223
+
1224
+ _ = v.BindEnv("openai.api_key", "OPENAI_API_KEY")
1225
+ _ = v.BindEnv("openai.model", "OPENAI_MODEL")
1226
+ _ = v.BindEnv("openai.base_url", "OPENAI_BASE_URL")
1227
+
1228
+ _ = v.BindEnv("anthropic.api_key", "ANTHROPIC_API_KEY")
1229
+ _ = v.BindEnv("anthropic.model", "ANTHROPIC_MODEL")
1230
+
1231
+ _ = v.BindEnv("kafka.brokers", "KAFKA_BROKERS")
1232
+ _ = v.BindEnv("kafka.client_id", "KAFKA_CLIENT_ID")
1233
+ _ = v.BindEnv("kafka.group_id", "KAFKA_GROUP_ID")
1234
+ _ = v.BindEnv("kafka.sasl_username", "KAFKA_SASL_USERNAME")
1235
+ _ = v.BindEnv("kafka.sasl_password", "KAFKA_SASL_PASSWORD")
1236
+ _ = v.BindEnv("kafka.ssl_enabled", "KAFKA_SSL_ENABLED")
1237
+
1238
+ _ = v.BindEnv("log.level", "LOG_LEVEL")
1239
+ _ = v.BindEnv("log.env", "LOG_ENV")
1240
+ _ = v.BindEnv("log.request_success_level", "LOG_REQUEST_SUCCESS_LEVEL")
1241
+ _ = v.BindEnv("log.request_success_sample_rate", "LOG_REQUEST_SUCCESS_SAMPLE_RATE")
1242
+ _ = v.BindEnv("log.request_slow_threshold", "LOG_REQUEST_SLOW_THRESHOLD")
1243
+
1244
+ _ = v.BindEnv("audit.access_log.enabled", "AUDIT_ACCESS_LOG_ENABLED")
1245
+ _ = v.BindEnv("audit.access_log.success_read_sample_rate", "AUDIT_ACCESS_LOG_SUCCESS_READ_SAMPLE_RATE")
1246
+ _ = v.BindEnv("audit.access_log.slow_request_threshold", "AUDIT_ACCESS_LOG_SLOW_REQUEST_THRESHOLD")
1247
+ _ = v.BindEnv("audit.access_log.record_write_methods", "AUDIT_ACCESS_LOG_RECORD_WRITE_METHODS")
1248
+ _ = v.BindEnv("audit.access_log.record_client_errors", "AUDIT_ACCESS_LOG_RECORD_CLIENT_ERRORS")
1249
+
1250
+ _ = v.BindEnv("throttle.ttl", "THROTTLE_TTL")
1251
+ _ = v.BindEnv("throttle.limit", "THROTTLE_LIMIT")
1252
+
1253
+ _ = v.BindEnv("encryption_key", "ENCRYPTION_KEY")
1254
+ _ = v.BindEnv("encryption_key_id", "ENCRYPTION_KEY_ID")
1255
+ _ = v.BindEnv("encryption_keyring_file", "ENCRYPTION_KEYRING_FILE")
1256
+
1257
+ _ = v.BindEnv("export.local_dir", "EXPORT_LOCAL_DIR")
1258
+ _ = v.BindEnv("export.path_prefix", "EXPORT_PATH_PREFIX")
1259
+
1260
+ _ = v.BindEnv("tracing.enabled", "TRACING_ENABLED")
1261
+ _ = v.BindEnv("tracing.endpoint", "TRACING_ENDPOINT")
1262
+ _ = v.BindEnv("tracing.service_name", "TRACING_SERVICE_NAME")
1263
+ _ = v.BindEnv("tracing.service_version", "TRACING_SERVICE_VERSION")
1264
+ _ = v.BindEnv("tracing.environment", "TRACING_ENVIRONMENT")
1265
+ _ = v.BindEnv("tracing.sampling_ratio", "TRACING_SAMPLING_RATIO")
1266
+
1267
+ _ = v.BindEnv("sentry.enabled", "SENTRY_ENABLED")
1268
+ _ = v.BindEnv("sentry.dsn", "SENTRY_DSN")
1269
+ _ = v.BindEnv("sentry.environment", "SENTRY_ENVIRONMENT")
1270
+ _ = v.BindEnv("sentry.release", "SENTRY_RELEASE")
1271
+ _ = v.BindEnv("sentry.sample_rate", "SENTRY_SAMPLE_RATE")
1272
+ _ = v.BindEnv("sentry.traces_sample_rate", "SENTRY_TRACES_SAMPLE_RATE")
1273
+ _ = v.BindEnv("sentry.debug", "SENTRY_DEBUG")
1274
+
1275
+ _ = v.BindEnv("clamav.enabled", "CLAMAV_ENABLED")
1276
+ _ = v.BindEnv("clamav.address", "CLAMAV_ADDRESS")
1277
+ _ = v.BindEnv("clamav.timeout", "CLAMAV_TIMEOUT")
1278
+
1279
+ _ = v.BindEnv("geoip.database_path", "GEOIP_DATABASE_PATH")
1280
+ if err := bindDocumentationEnvironment(v); err != nil {
1281
+ return err
1282
+ }
1283
+
1284
+ _ = v.BindEnv("temporal.enabled", "TEMPORAL_ENABLED")
1285
+ _ = v.BindEnv("temporal.host_port", "TEMPORAL_HOST_PORT")
1286
+ _ = v.BindEnv("temporal.namespace", "TEMPORAL_NAMESPACE")
1287
+ _ = v.BindEnv("temporal.tls.enabled", "TEMPORAL_TLS_ENABLED")
1288
+ _ = v.BindEnv("temporal.tls.server_name", "TEMPORAL_TLS_SERVER_NAME")
1289
+ _ = v.BindEnv("temporal.tls.disable_host_verification", "TEMPORAL_TLS_DISABLE_HOST_VERIFICATION")
1290
+ _ = v.BindEnv("temporal.tls.ca_path", "TEMPORAL_TLS_CA_PATH")
1291
+ _ = v.BindEnv("temporal.tls.cert_path", "TEMPORAL_TLS_CERT_PATH")
1292
+ _ = v.BindEnv("temporal.tls.key_path", "TEMPORAL_TLS_KEY_PATH")
1293
+ _ = v.BindEnv("temporal.worker.max_concurrent_activities", "TEMPORAL_WORKER_MAX_CONCURRENT_ACTIVITIES")
1294
+ _ = v.BindEnv("temporal.worker.max_concurrent_workflow_tasks", "TEMPORAL_WORKER_MAX_CONCURRENT_WORKFLOW_TASKS")
1295
+ _ = v.BindEnv("temporal.worker.max_concurrent_activity_task_pollers", "TEMPORAL_WORKER_MAX_CONCURRENT_ACTIVITY_TASK_POLLERS")
1296
+ _ = v.BindEnv("temporal.worker.max_concurrent_workflow_task_pollers", "TEMPORAL_WORKER_MAX_CONCURRENT_WORKFLOW_TASK_POLLERS")
1297
+ _ = v.BindEnv("temporal.provider_poll_worker.max_concurrent_activities", "TEMPORAL_PROVIDER_POLL_WORKER_MAX_CONCURRENT_ACTIVITIES")
1298
+ _ = v.BindEnv("temporal.provider_poll_worker.max_concurrent_workflow_tasks", "TEMPORAL_PROVIDER_POLL_WORKER_MAX_CONCURRENT_WORKFLOW_TASKS")
1299
+ _ = v.BindEnv("temporal.provider_poll_worker.max_concurrent_activity_task_pollers", "TEMPORAL_PROVIDER_POLL_WORKER_MAX_CONCURRENT_ACTIVITY_TASK_POLLERS")
1300
+ _ = v.BindEnv("temporal.provider_poll_worker.max_concurrent_workflow_task_pollers", "TEMPORAL_PROVIDER_POLL_WORKER_MAX_CONCURRENT_WORKFLOW_TASK_POLLERS")
1301
+
1302
+ _ = v.BindEnv("exchange_rate_api_key", "EXCHANGE_RATE_API_KEY")
1303
+
1304
+ _ = v.BindEnv("internal_service.platform_base_url", "INTERNAL_SERVICE_PLATFORM_BASE_URL", "PLATFORM_INTERNAL_BASE_URL")
1305
+ _ = v.BindEnv("integration_event.inbound.trust_registry_file", "ONEX_INTEGRATION_EVENT_INBOUND_TRUST_REGISTRY_FILE")
1306
+ _ = v.BindEnv("integration_event.inbound.runtime_artifact_file", "ONEX_INTEGRATION_EVENT_INBOUND_RUNTIME_ARTIFACT_FILE")
1307
+ _ = v.BindEnv("integration_event.inbound.release_manifest_file", "ONEX_INTEGRATION_EVENT_INBOUND_RELEASE_MANIFEST_FILE")
1308
+ _ = v.BindEnv("integration_event.inbound.migration_marker_file", "ONEX_INTEGRATION_EVENT_INBOUND_MIGRATION_MARKER_FILE")
1309
+ _ = v.BindEnv("internal_service.service_token_signing.key_id", "ONEX_IAM_SERVICE_TOKEN_SIGNING_KEY_ID")
1310
+ _ = v.BindEnv("internal_service.service_token_signing.private_key_path", "ONEX_IAM_SERVICE_TOKEN_PRIVATE_KEY_PATH")
1311
+ _ = v.BindEnv("internal_service.service_token_signing.public_key_directory", "ONEX_IAM_SERVICE_TOKEN_PUBLIC_KEY_DIRECTORY")
1312
+ _ = v.BindEnv("identity.access_token.delegated_token_ttl", "IDENTITY_DELEGATED_ACCESS_TOKEN_TTL")
1313
+ _ = v.BindEnv("internal_service.token_ttl_seconds", "ONEX_IAM_SERVICE_TOKEN_TTL_SECONDS")
1314
+ _ = v.BindEnv("internal_service.authorization_consistency_token_ttl_seconds", "ONEX_IAM_AUTHORIZATION_CONSISTENCY_TOKEN_TTL_SECONDS")
1315
+ _ = v.BindEnv("internal_service.request_timeout", "INTERNAL_SERVICE_REQUEST_TIMEOUT")
1316
+ _ = v.BindEnv("content_safety.aliyun.enabled", "CONTENT_SAFETY_ALIYUN_ENABLED")
1317
+ _ = v.BindEnv("content_safety.aliyun.provider_code", "CONTENT_SAFETY_ALIYUN_PROVIDER_CODE")
1318
+ _ = v.BindEnv("content_safety.aliyun.region_id", "CONTENT_SAFETY_ALIYUN_REGION_ID")
1319
+ _ = v.BindEnv("content_safety.aliyun.endpoint", "CONTENT_SAFETY_ALIYUN_ENDPOINT")
1320
+ _ = v.BindEnv("content_safety.aliyun.text_service", "CONTENT_SAFETY_ALIYUN_TEXT_SERVICE")
1321
+ _ = v.BindEnv("content_safety.aliyun.image_service", "CONTENT_SAFETY_ALIYUN_IMAGE_SERVICE")
1322
+ _ = v.BindEnv("content_safety.aliyun.video_service", "CONTENT_SAFETY_ALIYUN_VIDEO_SERVICE")
1323
+ _ = v.BindEnv("content_safety.aliyun.connect_timeout_ms", "CONTENT_SAFETY_ALIYUN_CONNECT_TIMEOUT_MS")
1324
+ _ = v.BindEnv("content_safety.aliyun.read_timeout_ms", "CONTENT_SAFETY_ALIYUN_READ_TIMEOUT_MS")
1325
+ _ = v.BindEnv("content_safety.aliyun.video_poll_initial_delay_ms", "CONTENT_SAFETY_ALIYUN_VIDEO_POLL_INITIAL_DELAY_MS")
1326
+ _ = v.BindEnv("content_safety.aliyun.video_poll_interval_ms", "CONTENT_SAFETY_ALIYUN_VIDEO_POLL_INTERVAL_MS")
1327
+ _ = v.BindEnv("content_safety.aliyun.video_poll_max_attempts", "CONTENT_SAFETY_ALIYUN_VIDEO_POLL_MAX_ATTEMPTS")
1328
+ _ = v.BindEnv("communication.reputation.ip_blocklist_zones", "COMMUNICATION_REPUTATION_IP_BLOCKLIST_ZONES")
1329
+ _ = v.BindEnv("communication.reputation.domain_blocklist_zones", "COMMUNICATION_REPUTATION_DOMAIN_BLOCKLIST_ZONES")
1330
+ _ = v.BindEnv("communication.reputation.dns_lookup_timeout_seconds", "COMMUNICATION_REPUTATION_DNS_LOOKUP_TIMEOUT_SECONDS")
1331
+ _ = v.BindEnv("communication.reputation.recheck_interval_hours", "COMMUNICATION_REPUTATION_RECHECK_INTERVAL_HOURS")
1332
+ _ = v.BindEnv("communication.reputation.recheck_batch_limit", "COMMUNICATION_REPUTATION_RECHECK_BATCH_LIMIT")
1333
+ _ = v.BindEnv("communication.unsubscribe_signing_secret", "COMMUNICATION_UNSUBSCRIBE_SIGNING_SECRET")
1334
+ _ = v.BindEnv("payment.risk_subject_hash_key", "PAYMENT_RISK_SUBJECT_HASH_KEY")
1335
+ _ = v.BindEnv("checkout.method_reference_signing_key", "CHECKOUT_METHOD_REFERENCE_SIGNING_KEY")
1336
+ _ = v.BindEnv("checkout.guest_actor_scope_signing_key", "CHECKOUT_GUEST_ACTOR_SCOPE_SIGNING_KEY")
1337
+ _ = v.BindEnv("checkout.replay_encryption_key_version", "CHECKOUT_REPLAY_ENCRYPTION_KEY_VERSION")
1338
+ _ = v.BindEnv("secret.active_driver_code", "SECRET_ACTIVE_DRIVER_CODE")
1339
+ _ = v.BindEnv("secret.fingerprint_key", "SECRET_FINGERPRINT_KEY")
1340
+ _ = v.BindEnv("secret.driver_health_timeout", "SECRET_DRIVER_HEALTH_TIMEOUT")
1341
+ _ = v.BindEnv("secret.envelope.driver_code", "SECRET_ENVELOPE_DRIVER_CODE")
1342
+ _ = v.BindEnv("secret.envelope.maximum_plaintext_bytes", "SECRET_ENVELOPE_MAXIMUM_PLAINTEXT_BYTES")
1343
+ _ = v.BindEnv("secret.runtime.service_account_code", "SECRET_RUNTIME_SERVICE_ACCOUNT_CODE")
1344
+ _ = v.BindEnv("secret.runtime.audience", "SECRET_RUNTIME_AUDIENCE")
1345
+ _ = v.BindEnv("secret.runtime.default_product_code", "SECRET_RUNTIME_DEFAULT_PRODUCT_CODE")
1346
+ _ = v.BindEnv("secret.runtime.lease_ttl", "SECRET_RUNTIME_LEASE_TTL")
1347
+ _ = v.BindEnv("secret.retention.minimum_destruction_delay", "SECRET_MINIMUM_DESTRUCTION_DELAY")
1348
+ _ = v.BindEnv("secret.retention.access_event_purge_enabled", "SECRET_ACCESS_EVENT_PURGE_ENABLED")
1349
+ _ = v.BindEnv("secret.retention.legal_hold_owner_resources", "SECRET_LEGAL_HOLD_OWNER_RESOURCES")
1350
+ _ = v.BindEnv("secret.worker.service_account_code", "SECRET_WORKER_SERVICE_ACCOUNT_CODE")
1351
+ _ = v.BindEnv("secret.worker.batch_size", "SECRET_WORKER_BATCH_SIZE")
1352
+ _ = v.BindEnv("secret.worker.partition_months_ahead", "SECRET_WORKER_PARTITION_MONTHS_AHEAD")
1353
+ _ = v.BindEnv("payment.store_purchase_reference_hash_key", "PAYMENT_STORE_PURCHASE_REFERENCE_HASH_KEY")
1354
+ _ = v.BindEnv("identity.phone_hash_key", "IDENTITY_PHONE_HASH_KEY")
1355
+ _ = v.BindEnv("identity.oauth_state_signing_secret", "IDENTITY_OAUTH_STATE_SIGNING_SECRET")
1356
+ _ = v.BindEnv("identity.access_token.issuer", "IDENTITY_ACCESS_TOKEN_ISSUER")
1357
+ _ = v.BindEnv("identity.access_token.signing_key_id", "IDENTITY_ACCESS_TOKEN_SIGNING_KEY_ID")
1358
+ _ = v.BindEnv("identity.access_token.private_key_path", "IDENTITY_ACCESS_TOKEN_PRIVATE_KEY_PATH")
1359
+ _ = v.BindEnv("identity.access_token.public_key_directory", "IDENTITY_ACCESS_TOKEN_PUBLIC_KEY_DIRECTORY")
1360
+ _ = v.BindEnv("identity.access_token.clock_skew_leeway", "IDENTITY_ACCESS_TOKEN_CLOCK_SKEW_LEEWAY")
1361
+ _ = v.BindEnv("identity.guest_capability.secret_provider_file", "IDENTITY_GUEST_CAPABILITY_SECRET_PROVIDER_FILE")
1362
+ _ = v.BindEnv("identity.guest_capability.initial_key_id", "IDENTITY_GUEST_CAPABILITY_INITIAL_KEY_ID")
1363
+ _ = v.BindEnv("identity.guest_capability.verifier_instance_id", "IDENTITY_GUEST_CAPABILITY_VERIFIER_INSTANCE_ID")
1364
+ _ = v.BindEnv("feature_flag.freshness_ttl", "FEATURE_FLAG_FRESHNESS_TTL")
1365
+ _ = v.BindEnv("feature_flag.maximum_stale_age", "FEATURE_FLAG_MAXIMUM_STALE_AGE")
1366
+ _ = v.BindEnv("feature_flag.cache_ttl", "FEATURE_FLAG_CACHE_TTL")
1367
+ return nil
1368
+ }
1369
+
1370
+ func validate(cfg *Config) error {
1371
+ if err := validateSecretBrokerConfig(cfg.App.Name, cfg.Secret); err != nil {
1372
+ return err
1373
+ }
1374
+ if requiresIdentityRuntimeConfig(cfg.App.Name) {
1375
+ if err := validateSecretConfig("IDENTITY_PHONE_HASH_KEY", cfg.Identity.PhoneHashKey, 32); err != nil {
1376
+ return err
1377
+ }
1378
+ if err := validateSecretConfig("IDENTITY_OAUTH_STATE_SIGNING_SECRET", cfg.Identity.OAuthStateSigningSecret, 32); err != nil {
1379
+ return err
1380
+ }
1381
+ if err := validateIdentityLegacyPasswordSchemes(cfg.Identity.LegacyPasswordSchemes); err != nil {
1382
+ return err
1383
+ }
1384
+ }
1385
+ if requiresIdentityAccessTokenRuntimeConfig(cfg.App.Name) {
1386
+ if err := validateIdentityAccessTokenConfig(cfg); err != nil {
1387
+ return err
1388
+ }
1389
+ }
1390
+ if cfg.IsIdentityAccessTokenIssuer() {
1391
+ if err := validateIdentityGuestCapabilityConfig(cfg.Identity.GuestCapability); err != nil {
1392
+ return err
1393
+ }
1394
+ }
1395
+ if err := validatePort(cfg.App.Port); err != nil {
1396
+ return err
1397
+ }
1398
+ if err := validateInternalServiceConfig(cfg); err != nil {
1399
+ return err
1400
+ }
1401
+ if err := validateDocumentationConfig(cfg); err != nil {
1402
+ return err
1403
+ }
1404
+ if err := validateAdminExperienceConfig(cfg.AdminExperience); err != nil {
1405
+ return err
1406
+ }
1407
+ if err := validateProductAdminConfig(cfg); err != nil {
1408
+ return err
1409
+ }
1410
+ if err := validateTemporalWorkerConfig(cfg.Temporal.Worker); err != nil {
1411
+ return err
1412
+ }
1413
+ if err := validateTemporalWorkerConfigWithPrefix(
1414
+ cfg.Temporal.ProviderPollWorker,
1415
+ "TEMPORAL_PROVIDER_POLL_WORKER",
1416
+ ); err != nil {
1417
+ return err
1418
+ }
1419
+ if requiresFeatureFlagRuntimeConfig(cfg.App.Name) {
1420
+ if err := validateFeatureFlagConfig(cfg.FeatureFlag); err != nil {
1421
+ return err
1422
+ }
1423
+ }
1424
+ return nil
1425
+ }
1426
+
1427
+ func requiresFeatureFlagRuntimeConfig(appName string) bool {
1428
+ switch strings.ToLower(strings.TrimSpace(appName)) {
1429
+ case "platform-api", "admin-api", "platform", "onex-platform":
1430
+ return true
1431
+ default:
1432
+ return false
1433
+ }
1434
+ }
1435
+
1436
+ func validateFeatureFlagConfig(cfg FeatureFlagConfig) error {
1437
+ if cfg.FreshnessTTL <= 0 {
1438
+ return fmt.Errorf("required config FEATURE_FLAG_FRESHNESS_TTL must be positive")
1439
+ }
1440
+ if cfg.MaximumStaleAge < cfg.FreshnessTTL {
1441
+ return fmt.Errorf("required config FEATURE_FLAG_MAXIMUM_STALE_AGE must not be shorter than freshness TTL")
1442
+ }
1443
+ if cfg.CacheTTL < cfg.MaximumStaleAge {
1444
+ return fmt.Errorf("required config FEATURE_FLAG_CACHE_TTL must not be shorter than maximum stale age")
1445
+ }
1446
+ return nil
1447
+ }
1448
+
1449
+ var (
1450
+ adminExperienceProductCodePattern = regexp.MustCompile(`^[a-z][a-z0-9_]{1,49}$`)
1451
+ adminExperienceDatasetKeyPattern = regexp.MustCompile(`^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$`)
1452
+ adminExperienceHashPattern = regexp.MustCompile(`^[0-9a-f]{64}$`)
1453
+ identityAccessTokenKeyIDPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,64}$`)
1454
+ identityAccessTokenScopePattern = regexp.MustCompile(`^[A-Za-z0-9._:-]+$`)
1455
+ identityGuestCapabilityKeyIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{7,63}$`)
1456
+ identityLegacyPasswordSchemePattern = regexp.MustCompile(`^[a-z][a-z0-9_]{2,63}$`)
1457
+ identityLegacyPasswordDriverPattern = regexp.MustCompile(`^[a-z][a-z0-9_]{2,63}$`)
1458
+ internalServiceIdentityNamePattern = regexp.MustCompile(`^[a-z][a-z0-9_]{1,63}$`)
1459
+ serviceSignatureKeyIDPattern = regexp.MustCompile(`^[a-z][a-z0-9_]{2,79}$`)
1460
+ serviceAccountCodePattern = regexp.MustCompile(`^svc_[a-z0-9_]{3,95}$`)
1461
+ identityClientIDPattern = regexp.MustCompile(`^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$`)
1462
+ identityApplicationCodePattern = regexp.MustCompile(`^[a-z][a-z0-9_]{1,79}$`)
1463
+ secretDriverCodePattern = regexp.MustCompile(`^[a-z][a-z0-9._-]{0,63}$`)
1464
+ )
1465
+
1466
+ func validateSecretBrokerConfig(appName string, cfg SecretConfig) error {
1467
+ activeDriverCode := strings.TrimSpace(cfg.ActiveDriverCode)
1468
+ envelopeDriverCode := strings.TrimSpace(cfg.Envelope.DriverCode)
1469
+ runtimeServiceAccountCode := strings.TrimSpace(cfg.Runtime.ServiceAccountCode)
1470
+ workerServiceAccountCode := strings.TrimSpace(cfg.Worker.ServiceAccountCode)
1471
+ configured := activeDriverCode != "" || envelopeDriverCode != "" || strings.TrimSpace(cfg.FingerprintKey) != "" ||
1472
+ runtimeServiceAccountCode != "" || workerServiceAccountCode != ""
1473
+ if !configured {
1474
+ return nil
1475
+ }
1476
+ if activeDriverCode != cfg.ActiveDriverCode || !secretDriverCodePattern.MatchString(activeDriverCode) {
1477
+ return fmt.Errorf("required config SECRET_ACTIVE_DRIVER_CODE is invalid")
1478
+ }
1479
+ if envelopeDriverCode != cfg.Envelope.DriverCode || !secretDriverCodePattern.MatchString(envelopeDriverCode) {
1480
+ return fmt.Errorf("required config SECRET_ENVELOPE_DRIVER_CODE is invalid")
1481
+ }
1482
+ if err := validateSecretConfig("SECRET_FINGERPRINT_KEY", cfg.FingerprintKey, 32); err != nil {
1483
+ return err
1484
+ }
1485
+ if cfg.DriverHealthTimeout <= 0 || cfg.DriverHealthTimeout > 30*time.Second {
1486
+ return fmt.Errorf("required config SECRET_DRIVER_HEALTH_TIMEOUT must be between zero and thirty seconds")
1487
+ }
1488
+ if cfg.Envelope.MaximumPlaintextBytes <= 0 || cfg.Envelope.MaximumPlaintextBytes > 256*1024 {
1489
+ return fmt.Errorf("required config SECRET_ENVELOPE_MAXIMUM_PLAINTEXT_BYTES must be between 1 and 262144")
1490
+ }
1491
+ if runtimeServiceAccountCode != cfg.Runtime.ServiceAccountCode || !serviceAccountCodePattern.MatchString(runtimeServiceAccountCode) {
1492
+ return fmt.Errorf("required config SECRET_RUNTIME_SERVICE_ACCOUNT_CODE is invalid")
1493
+ }
1494
+ if err := serviceidentity.ValidateAudience(cfg.Runtime.Audience); err != nil {
1495
+ return fmt.Errorf("required config SECRET_RUNTIME_AUDIENCE is invalid: %w", err)
1496
+ }
1497
+ defaultProductCode := strings.TrimSpace(cfg.Runtime.DefaultProductCode)
1498
+ if defaultProductCode != cfg.Runtime.DefaultProductCode || !adminExperienceProductCodePattern.MatchString(defaultProductCode) {
1499
+ return fmt.Errorf("required config SECRET_RUNTIME_DEFAULT_PRODUCT_CODE is invalid")
1500
+ }
1501
+ if cfg.Runtime.LeaseTTL < 30*time.Second || cfg.Runtime.LeaseTTL > 5*time.Minute {
1502
+ return fmt.Errorf("required config SECRET_RUNTIME_LEASE_TTL must be between thirty and three hundred seconds")
1503
+ }
1504
+ if cfg.Retention.MinimumDestructionDelay < time.Hour || cfg.Retention.MinimumDestructionDelay > 30*24*time.Hour {
1505
+ return fmt.Errorf("required config SECRET_MINIMUM_DESTRUCTION_DELAY must be between one hour and thirty days")
1506
+ }
1507
+ for _, ownerResource := range cfg.Retention.LegalHoldOwnerResources {
1508
+ if ownerResource == "" || ownerResource != strings.TrimSpace(ownerResource) {
1509
+ return fmt.Errorf("required config SECRET_LEGAL_HOLD_OWNER_RESOURCES contains an invalid value")
1510
+ }
1511
+ }
1512
+ if strings.TrimSpace(appName) != "worker" {
1513
+ return nil
1514
+ }
1515
+ if workerServiceAccountCode != cfg.Worker.ServiceAccountCode || !serviceAccountCodePattern.MatchString(workerServiceAccountCode) {
1516
+ return fmt.Errorf("required config SECRET_WORKER_SERVICE_ACCOUNT_CODE is invalid")
1517
+ }
1518
+ if cfg.Worker.BatchSize <= 0 || cfg.Worker.BatchSize > 500 {
1519
+ return fmt.Errorf("required config SECRET_WORKER_BATCH_SIZE must be between 1 and 500")
1520
+ }
1521
+ if cfg.Worker.PartitionMonthsAhead <= 0 || cfg.Worker.PartitionMonthsAhead > 24 {
1522
+ return fmt.Errorf("required config SECRET_WORKER_PARTITION_MONTHS_AHEAD must be between 1 and 24")
1523
+ }
1524
+ return nil
1525
+ }
1526
+
1527
+ func validateIdentityLegacyPasswordSchemes(schemes map[string]IdentityLegacyPasswordSchemeConfig) error {
1528
+ for schemeID, scheme := range schemes {
1529
+ if !identityLegacyPasswordSchemePattern.MatchString(schemeID) {
1530
+ return fmt.Errorf("identity legacy password scheme %q is invalid", schemeID)
1531
+ }
1532
+ if !identityLegacyPasswordDriverPattern.MatchString(scheme.Driver) {
1533
+ return fmt.Errorf("identity legacy password scheme %q driver is invalid", schemeID)
1534
+ }
1535
+ if err := validateSecretReferenceValue(
1536
+ "identity.legacy_password_schemes["+schemeID+"].secret_ref",
1537
+ scheme.SecretRef,
1538
+ ); err != nil {
1539
+ return err
1540
+ }
1541
+ retireAt, err := time.Parse(time.RFC3339, strings.TrimSpace(scheme.RetireAt))
1542
+ if err != nil || retireAt.Format(time.RFC3339) != scheme.RetireAt {
1543
+ return fmt.Errorf("identity legacy password scheme %q retire_at is invalid", schemeID)
1544
+ }
1545
+ }
1546
+ return nil
1547
+ }
1548
+
1549
+ func validateAdminExperienceConfig(cfg AdminExperienceConfig) error {
1550
+ products := make(map[string]struct{}, len(cfg.ExternalProducts))
1551
+ datasets := make(map[string]struct{})
1552
+ for i := range cfg.ExternalProducts {
1553
+ product := cfg.ExternalProducts[i]
1554
+ productCode := strings.TrimSpace(product.ProductCode)
1555
+ if productCode != product.ProductCode || !adminExperienceProductCodePattern.MatchString(productCode) {
1556
+ return fmt.Errorf("admin_experience.external_products[%d].product_code is invalid", i)
1557
+ }
1558
+ if _, duplicate := products[productCode]; duplicate {
1559
+ return fmt.Errorf("admin_experience external product %q is duplicated", productCode)
1560
+ }
1561
+ products[productCode] = struct{}{}
1562
+ endpoint, err := url.Parse(strings.TrimSpace(product.BaseURL))
1563
+ if err != nil || endpoint.Host == "" || (endpoint.Scheme != "http" && endpoint.Scheme != "https") ||
1564
+ endpoint.User != nil || endpoint.RawQuery != "" || endpoint.Fragment != "" {
1565
+ return fmt.Errorf("admin_experience external product %q base_url is invalid", productCode)
1566
+ }
1567
+ if len(product.Datasets) == 0 {
1568
+ return fmt.Errorf("admin_experience external product %q requires at least one dataset", productCode)
1569
+ }
1570
+ for j := range product.Datasets {
1571
+ dataset := product.Datasets[j]
1572
+ if !adminExperienceDatasetKeyPattern.MatchString(dataset.Key) || !strings.HasPrefix(dataset.Key, productCode+".") {
1573
+ return fmt.Errorf("admin_experience external product %q dataset %d key is invalid", productCode, j)
1574
+ }
1575
+ if !adminExperienceHashPattern.MatchString(dataset.DescriptorHash) {
1576
+ return fmt.Errorf("admin_experience external dataset %q descriptor_hash is invalid", dataset.Key)
1577
+ }
1578
+ if _, duplicate := datasets[dataset.Key]; duplicate {
1579
+ return fmt.Errorf("admin_experience external dataset %q is duplicated", dataset.Key)
1580
+ }
1581
+ datasets[dataset.Key] = struct{}{}
1582
+ }
1583
+ }
1584
+ return nil
1585
+ }
1586
+
1587
+ func validateProductAdminConfig(cfg *Config) error {
1588
+ if cfg == nil {
1589
+ return fmt.Errorf("product-admin config is required")
1590
+ }
1591
+ if !strings.EqualFold(strings.TrimSpace(cfg.App.Name), "admin-api") {
1592
+ return nil
1593
+ }
1594
+ if len(cfg.ProductAdmin.Targets) == 0 {
1595
+ return nil
1596
+ }
1597
+ for productCode, target := range cfg.ProductAdmin.Targets {
1598
+ if !adminExperienceProductCodePattern.MatchString(productCode) {
1599
+ return fmt.Errorf("product_admin target product code %q is invalid", productCode)
1600
+ }
1601
+ prefix := fmt.Sprintf("PRODUCT_ADMIN_TARGETS[%q]", productCode)
1602
+ baseURL, err := validateServiceBaseURL(prefix+".base_url", target.BaseURL)
1603
+ if err != nil {
1604
+ return err
1605
+ }
1606
+ if baseURL.Path != "" && baseURL.Path != "/" {
1607
+ return fmt.Errorf("required config %s.base_url must be an origin URL", prefix)
1608
+ }
1609
+ if err := validateServiceEndpoint(
1610
+ cfg,
1611
+ prefix+".base_url",
1612
+ baseURL,
1613
+ prefix+".allowed_origin",
1614
+ target.AllowedOrigin,
1615
+ ); err != nil {
1616
+ return err
1617
+ }
1618
+ if target.RequestTimeout < time.Second || target.RequestTimeout > time.Minute {
1619
+ return fmt.Errorf("invalid %s.request_timeout: must be between 1s and 1m", prefix)
1620
+ }
1621
+ identityName := strings.TrimSpace(target.ServiceIdentityName)
1622
+ if identityName != target.ServiceIdentityName || !internalServiceIdentityNamePattern.MatchString(identityName) {
1623
+ return fmt.Errorf("required config %s.service_identity_name is invalid", prefix)
1624
+ }
1625
+ if _, exists := cfg.InternalService.OutboundIdentities[identityName]; !exists {
1626
+ return fmt.Errorf("required internal service identity %q is not configured", identityName)
1627
+ }
1628
+ if !serviceSignatureKeyIDPattern.MatchString(target.SignatureKeyID) {
1629
+ return fmt.Errorf("required config %s.signature_key_id is invalid", prefix)
1630
+ }
1631
+ if err := validateSecretReferenceValue(prefix+".signature_secret_ref", target.SignatureSecretRef); err != nil {
1632
+ return err
1633
+ }
1634
+ }
1635
+ return nil
1636
+ }
1637
+
1638
+ func validateServiceEndpoint(
1639
+ cfg *Config,
1640
+ baseURLName string,
1641
+ endpoint *url.URL,
1642
+ allowedOriginName string,
1643
+ allowedOriginValue string,
1644
+ ) error {
1645
+ allowedOrigin, err := validateServiceBaseURL(allowedOriginName, allowedOriginValue)
1646
+ if err != nil {
1647
+ return err
1648
+ }
1649
+ if allowedOrigin.Path != "" && allowedOrigin.Path != "/" {
1650
+ return fmt.Errorf("required config %s must be an origin URL", allowedOriginName)
1651
+ }
1652
+ if serviceOrigin(endpoint) != serviceOrigin(allowedOrigin) {
1653
+ return fmt.Errorf("required config %s is not allowed by %s", baseURLName, allowedOriginName)
1654
+ }
1655
+ if requiresServiceTLS(cfg.App.Env) && endpoint.Scheme != "https" {
1656
+ return fmt.Errorf("required config %s must use https in this environment", baseURLName)
1657
+ }
1658
+ if isLocalEnvironment(cfg.App.Env) && endpoint.Scheme == "http" && !isLoopbackServiceHost(endpoint.Hostname()) {
1659
+ return fmt.Errorf("required config %s may use http only on a loopback host in local development", baseURLName)
1660
+ }
1661
+ return nil
1662
+ }
1663
+
1664
+ func requiresServiceTLS(environment string) bool {
1665
+ environment = strings.ToLower(strings.TrimSpace(environment))
1666
+ return !isLocalEnvironment(environment) && environment != "docker"
1667
+ }
1668
+
1669
+ func isLocalEnvironment(environment string) bool {
1670
+ switch strings.ToLower(strings.TrimSpace(environment)) {
1671
+ case "", "local", "development", "dev":
1672
+ return true
1673
+ default:
1674
+ return false
1675
+ }
1676
+ }
1677
+
1678
+ func isLoopbackServiceHost(host string) bool {
1679
+ host = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(host)), ".")
1680
+ if host == "localhost" {
1681
+ return true
1682
+ }
1683
+ address, err := netip.ParseAddr(host)
1684
+ return err == nil && address.IsLoopback()
1685
+ }
1686
+
1687
+ func serviceOrigin(endpoint *url.URL) string {
1688
+ return strings.ToLower(endpoint.Scheme + "://" + endpoint.Host)
1689
+ }
1690
+
1691
+ func validateServiceBaseURL(name, value string) (*url.URL, error) {
1692
+ trimmed := strings.TrimSpace(value)
1693
+ endpoint, err := url.Parse(trimmed)
1694
+ if err != nil || trimmed != value || endpoint.Host == "" ||
1695
+ (endpoint.Scheme != "http" && endpoint.Scheme != "https") || endpoint.Opaque != "" ||
1696
+ endpoint.User != nil || endpoint.RawQuery != "" || endpoint.Fragment != "" {
1697
+ return nil, fmt.Errorf("required config %s is invalid", name)
1698
+ }
1699
+ return endpoint, nil
1700
+ }
1701
+
1702
+ func requiresIdentityRuntimeConfig(appName string) bool {
1703
+ switch strings.ToLower(strings.TrimSpace(appName)) {
1704
+ case "platform-api", "admin-api", "worker", "platform", "onex-platform":
1705
+ return true
1706
+ default:
1707
+ return false
1708
+ }
1709
+ }
1710
+
1711
+ func requiresIdentityAccessTokenRuntimeConfig(appName string) bool {
1712
+ return requiresIdentityRuntimeConfig(appName)
1713
+ }
1714
+
1715
+ func validateIdentityAccessTokenConfig(cfg *Config) error {
1716
+ accessToken := cfg.Identity.AccessToken
1717
+ issuer, err := url.Parse(strings.TrimSpace(accessToken.Issuer))
1718
+ if err != nil || issuer.Scheme == "" || issuer.Host == "" || issuer.Opaque != "" || issuer.Path != "" ||
1719
+ issuer.User != nil || issuer.RawQuery != "" || issuer.Fragment != "" || strings.HasSuffix(accessToken.Issuer, "/") {
1720
+ return fmt.Errorf("required config IDENTITY_ACCESS_TOKEN_ISSUER is invalid")
1721
+ }
1722
+ if err := validateIdentityIssuerTransport(cfg, issuer); err != nil {
1723
+ return err
1724
+ }
1725
+ if !identityAccessTokenKeyIDPattern.MatchString(accessToken.SigningKeyID) {
1726
+ return fmt.Errorf("required config IDENTITY_ACCESS_TOKEN_SIGNING_KEY_ID is invalid")
1727
+ }
1728
+ if err := validateReferenceConfig("IDENTITY_ACCESS_TOKEN_PUBLIC_KEY_DIRECTORY", accessToken.PublicKeyDirectory); err != nil {
1729
+ return err
1730
+ }
1731
+ if accessToken.ClockSkewLeeway < 0 || accessToken.ClockSkewLeeway > maxIdentityClockSkewLeeway {
1732
+ return fmt.Errorf("invalid IDENTITY_ACCESS_TOKEN_CLOCK_SKEW_LEEWAY: must be between 0s and 1m")
1733
+ }
1734
+ if accessToken.DelegatedTokenTTL <= 0 || accessToken.DelegatedTokenTTL > time.Duration(maxDelegatedAccessTokenTTLSeconds)*time.Second {
1735
+ return fmt.Errorf("invalid IDENTITY_DELEGATED_ACCESS_TOKEN_TTL: must be between 1s and 15m")
1736
+ }
1737
+ if cfg.IsIdentityAccessTokenIssuer() {
1738
+ if err := validateReferenceConfig("IDENTITY_ACCESS_TOKEN_PRIVATE_KEY_PATH", accessToken.PrivateKeyPath); err != nil {
1739
+ return err
1740
+ }
1741
+ }
1742
+ if len(accessToken.Resources) == 0 {
1743
+ return fmt.Errorf("identity.access_token.resources requires at least one resource")
1744
+ }
1745
+ if err := validateIdentityProtectedResources(cfg, accessToken.ProtectedResources); err != nil {
1746
+ return err
1747
+ }
1748
+ audiences := make(map[string]string, len(accessToken.Resources))
1749
+ for productCode, resource := range accessToken.Resources {
1750
+ if !adminExperienceProductCodePattern.MatchString(productCode) {
1751
+ return fmt.Errorf("identity.access_token.resources product code %q is invalid", productCode)
1752
+ }
1753
+ audienceValue := strings.TrimSpace(resource.Audience)
1754
+ audience, err := url.Parse(audienceValue)
1755
+ if err != nil || audienceValue != resource.Audience || audience.Scheme == "" || strings.ContainsFunc(resource.Audience, unicode.IsSpace) {
1756
+ return fmt.Errorf("identity.access_token.resources[%q].audience is invalid", productCode)
1757
+ }
1758
+ if existingProductCode, duplicate := audiences[audienceValue]; duplicate {
1759
+ return fmt.Errorf(
1760
+ "identity.access_token.resources[%q].audience duplicates resource %q",
1761
+ productCode,
1762
+ existingProductCode,
1763
+ )
1764
+ }
1765
+ audiences[audienceValue] = productCode
1766
+ if err := validateAccessTokenScopes(productCode, "user_scopes", resource.UserScopes); err != nil {
1767
+ return err
1768
+ }
1769
+ if err := validateAccessTokenScopes(productCode, "guest_scopes", resource.GuestScopes); err != nil {
1770
+ return err
1771
+ }
1772
+ }
1773
+ return validateIdentityClients(cfg.Identity.Clients, accessToken.Resources, accessToken.ProtectedResources)
1774
+ }
1775
+
1776
+ func validateIdentityProtectedResources(cfg *Config, resources map[string]IdentityProtectedResourceConfig) error {
1777
+ if len(resources) == 0 {
1778
+ return fmt.Errorf("identity.access_token.protected_resources requires at least one resource")
1779
+ }
1780
+ identifiers := make(map[string]string, len(resources))
1781
+ audiences := make(map[string]string, len(resources))
1782
+ for code, resource := range resources {
1783
+ if !adminExperienceProductCodePattern.MatchString(code) {
1784
+ return fmt.Errorf("identity.access_token.protected_resources resource code %q is invalid", code)
1785
+ }
1786
+ if err := serviceidentity.ValidateResourceIdentifier(resource.ResourceIdentifier); err != nil {
1787
+ return fmt.Errorf("identity.access_token.protected_resources[%q].resource_identifier is invalid", code)
1788
+ }
1789
+ parsed, _ := url.Parse(resource.ResourceIdentifier)
1790
+ if cfg.IsProduction() && parsed.Scheme != "https" {
1791
+ return fmt.Errorf("identity.access_token.protected_resources[%q].resource_identifier must use https in production", code)
1792
+ }
1793
+ if existingCode, duplicate := identifiers[resource.ResourceIdentifier]; duplicate {
1794
+ return fmt.Errorf("identity.access_token.protected_resources[%q].resource_identifier duplicates resource %q", code, existingCode)
1795
+ }
1796
+ identifiers[resource.ResourceIdentifier] = code
1797
+ if err := serviceidentity.ValidateAudience(resource.Audience); err != nil ||
1798
+ resource.Audience == serviceidentity.AudienceIdentityTokenService {
1799
+ return fmt.Errorf("identity.access_token.protected_resources[%q].audience is invalid", code)
1800
+ }
1801
+ if existingCode, duplicate := audiences[resource.Audience]; duplicate {
1802
+ return fmt.Errorf("identity.access_token.protected_resources[%q].audience duplicates resource %q", code, existingCode)
1803
+ }
1804
+ audiences[resource.Audience] = code
1805
+ if strings.TrimSpace(resource.ResourceName) != resource.ResourceName || resource.ResourceName == "" || len(resource.ResourceName) > 160 {
1806
+ return fmt.Errorf("identity.access_token.protected_resources[%q].resource_name is invalid", code)
1807
+ }
1808
+ }
1809
+ return nil
1810
+ }
1811
+
1812
+ func validateIdentityClients(
1813
+ clients map[string]IdentityClientConfig,
1814
+ resources map[string]IdentityAccessTokenResourceConfig,
1815
+ protectedResources map[string]IdentityProtectedResourceConfig,
1816
+ ) error {
1817
+ if len(clients) == 0 {
1818
+ return fmt.Errorf("identity.clients requires at least one client")
1819
+ }
1820
+ registeredResourceAudiences := make(map[string]struct{}, len(protectedResources))
1821
+ for _, resource := range protectedResources {
1822
+ registeredResourceAudiences[resource.Audience] = struct{}{}
1823
+ }
1824
+ for clientID, client := range clients {
1825
+ if strings.TrimSpace(clientID) != clientID || !identityClientIDPattern.MatchString(clientID) {
1826
+ return fmt.Errorf("identity.clients client id %q is invalid", clientID)
1827
+ }
1828
+ productCode := strings.TrimSpace(client.ProductCode)
1829
+ if productCode != client.ProductCode || !adminExperienceProductCodePattern.MatchString(productCode) {
1830
+ return fmt.Errorf("identity.clients[%q].product_code is invalid", clientID)
1831
+ }
1832
+ if _, ok := resources[productCode]; !ok {
1833
+ return fmt.Errorf("identity.clients[%q].product_code references unknown resource %q", clientID, productCode)
1834
+ }
1835
+ applicationCode := strings.TrimSpace(client.ApplicationCode)
1836
+ if applicationCode != client.ApplicationCode || !identityApplicationCodePattern.MatchString(applicationCode) {
1837
+ return fmt.Errorf("identity.clients[%q].application_code is invalid", clientID)
1838
+ }
1839
+ registrationSource := strings.TrimSpace(client.RegistrationSource)
1840
+ if registrationSource != client.RegistrationSource || !adminExperienceProductCodePattern.MatchString(registrationSource) {
1841
+ return fmt.Errorf("identity.clients[%q].registration_source is invalid", clientID)
1842
+ }
1843
+ if len(client.AllowedPrincipalTypes) == 0 {
1844
+ return fmt.Errorf("identity.clients[%q].allowed_principal_types requires at least one principal type", clientID)
1845
+ }
1846
+ seenPrincipalTypes := make(map[string]struct{}, len(client.AllowedPrincipalTypes))
1847
+ for _, principalType := range client.AllowedPrincipalTypes {
1848
+ if strings.TrimSpace(principalType) != principalType || (principalType != "user" && principalType != "guest") {
1849
+ return fmt.Errorf("identity.clients[%q].allowed_principal_types contains invalid principal type %q", clientID, principalType)
1850
+ }
1851
+ if _, duplicate := seenPrincipalTypes[principalType]; duplicate {
1852
+ return fmt.Errorf("identity.clients[%q].allowed_principal_types contains duplicate principal type %q", clientID, principalType)
1853
+ }
1854
+ seenPrincipalTypes[principalType] = struct{}{}
1855
+ }
1856
+ seenServiceAccounts := make(map[string]struct{}, len(client.TokenExchangeServiceAccounts))
1857
+ for _, serviceAccountCode := range client.TokenExchangeServiceAccounts {
1858
+ if strings.TrimSpace(serviceAccountCode) != serviceAccountCode || !serviceAccountCodePattern.MatchString(serviceAccountCode) {
1859
+ return fmt.Errorf("identity.clients[%q].token_exchange_service_accounts contains invalid service account %q", clientID, serviceAccountCode)
1860
+ }
1861
+ if _, duplicate := seenServiceAccounts[serviceAccountCode]; duplicate {
1862
+ return fmt.Errorf("identity.clients[%q].token_exchange_service_accounts contains duplicate service account %q", clientID, serviceAccountCode)
1863
+ }
1864
+ seenServiceAccounts[serviceAccountCode] = struct{}{}
1865
+ }
1866
+ seenResources := make(map[string]struct{}, len(client.TokenExchangeResources))
1867
+ for _, audience := range client.TokenExchangeResources {
1868
+ if err := serviceidentity.ValidateAudience(audience); err != nil || audience == serviceidentity.AudienceIdentityTokenService {
1869
+ return fmt.Errorf("identity.clients[%q].token_exchange_resources contains invalid audience %q", clientID, audience)
1870
+ }
1871
+ if _, registered := registeredResourceAudiences[audience]; !registered {
1872
+ return fmt.Errorf("identity.clients[%q].token_exchange_resources references unknown protected resource audience %q", clientID, audience)
1873
+ }
1874
+ if _, duplicate := seenResources[audience]; duplicate {
1875
+ return fmt.Errorf("identity.clients[%q].token_exchange_resources contains duplicate audience %q", clientID, audience)
1876
+ }
1877
+ seenResources[audience] = struct{}{}
1878
+ }
1879
+ if (len(client.TokenExchangeServiceAccounts) == 0) != (len(client.TokenExchangeResources) == 0) {
1880
+ return fmt.Errorf("identity.clients[%q] token exchange actors and resources must be configured together", clientID)
1881
+ }
1882
+ }
1883
+ return nil
1884
+ }
1885
+
1886
+ func validateIdentityGuestCapabilityConfig(cfg IdentityGuestCapabilityConfig) error {
1887
+ if err := validatePathValue("IDENTITY_GUEST_CAPABILITY_SECRET_PROVIDER_FILE", cfg.SecretProviderFile, true); err != nil {
1888
+ return err
1889
+ }
1890
+ if !identityGuestCapabilityKeyIDPattern.MatchString(cfg.InitialKeyID) {
1891
+ return fmt.Errorf("required config IDENTITY_GUEST_CAPABILITY_INITIAL_KEY_ID is invalid")
1892
+ }
1893
+ if err := validateReferenceConfig("IDENTITY_GUEST_CAPABILITY_VERIFIER_INSTANCE_ID", cfg.VerifierInstanceID); err != nil {
1894
+ return err
1895
+ }
1896
+ if len(cfg.VerifierInstanceID) > 128 {
1897
+ return fmt.Errorf("required config IDENTITY_GUEST_CAPABILITY_VERIFIER_INSTANCE_ID must not exceed 128 characters")
1898
+ }
1899
+ return nil
1900
+ }
1901
+
1902
+ func validateIdentityIssuerTransport(cfg *Config, issuer *url.URL) error {
1903
+ if issuer.Scheme == "https" {
1904
+ return nil
1905
+ }
1906
+ if issuer.Scheme == "http" && isIdentityLocalRuntimeEnvironment(cfg.App.Env) && isLoopbackServiceHost(issuer.Hostname()) {
1907
+ return nil
1908
+ }
1909
+ if cfg.IsProduction() {
1910
+ return fmt.Errorf("required config IDENTITY_ACCESS_TOKEN_ISSUER must use https in production")
1911
+ }
1912
+ return fmt.Errorf("required config IDENTITY_ACCESS_TOKEN_ISSUER may use http only on a loopback host in local development")
1913
+ }
1914
+
1915
+ func isIdentityLocalRuntimeEnvironment(environment string) bool {
1916
+ return isLocalEnvironment(environment) || strings.EqualFold(strings.TrimSpace(environment), "docker")
1917
+ }
1918
+
1919
+ func validateAccessTokenScopes(productCode, field string, scopes []string) error {
1920
+ if len(scopes) == 0 {
1921
+ return fmt.Errorf("identity.access_token.resources[%q].%s requires at least one scope", productCode, field)
1922
+ }
1923
+ seen := make(map[string]struct{}, len(scopes))
1924
+ for _, scope := range scopes {
1925
+ if !identityAccessTokenScopePattern.MatchString(scope) {
1926
+ return fmt.Errorf("identity.access_token.resources[%q].%s contains invalid scope %q", productCode, field, scope)
1927
+ }
1928
+ if _, duplicate := seen[scope]; duplicate {
1929
+ return fmt.Errorf("identity.access_token.resources[%q].%s contains duplicate scope %q", productCode, field, scope)
1930
+ }
1931
+ seen[scope] = struct{}{}
1932
+ }
1933
+ return nil
1934
+ }
1935
+
1936
+ func validatePort(port int) error {
1937
+ if port < 1 || port > 65535 {
1938
+ return fmt.Errorf("invalid APP_PORT: %d, must be 1-65535", port)
1939
+ }
1940
+ return nil
1941
+ }
1942
+
1943
+ func validateInternalServiceConfig(cfg *Config) error {
1944
+ internal := cfg.InternalService
1945
+ if internal.RequestTimeout < time.Second || internal.RequestTimeout > time.Minute {
1946
+ return fmt.Errorf("invalid INTERNAL_SERVICE_REQUEST_TIMEOUT: must be between 1s and 1m")
1947
+ }
1948
+ if internal.TokenTTLSeconds < 1 || internal.TokenTTLSeconds > maxInternalServiceTokenTTLSeconds {
1949
+ return fmt.Errorf("invalid ONEX_IAM_SERVICE_TOKEN_TTL_SECONDS: %d, must be 1-%d", internal.TokenTTLSeconds, maxInternalServiceTokenTTLSeconds)
1950
+ }
1951
+ if internal.AuthorizationConsistencyTokenTTLSeconds < 1 ||
1952
+ internal.AuthorizationConsistencyTokenTTLSeconds > maxIAMConsistencyTokenTTLSeconds {
1953
+ return fmt.Errorf(
1954
+ "invalid ONEX_IAM_AUTHORIZATION_CONSISTENCY_TOKEN_TTL_SECONDS: %d, must be 1-%d",
1955
+ internal.AuthorizationConsistencyTokenTTLSeconds,
1956
+ maxIAMConsistencyTokenTTLSeconds,
1957
+ )
1958
+ }
1959
+ signing := internal.ServiceTokenSigning
1960
+ if strings.TrimSpace(signing.PublicKeyDirectory) != "" || strings.TrimSpace(signing.PrivateKeyPath) != "" {
1961
+ if !identityAccessTokenKeyIDPattern.MatchString(signing.KeyID) {
1962
+ return fmt.Errorf("required config ONEX_IAM_SERVICE_TOKEN_SIGNING_KEY_ID is invalid")
1963
+ }
1964
+ if err := validateReferenceConfig("ONEX_IAM_SERVICE_TOKEN_PUBLIC_KEY_DIRECTORY", signing.PublicKeyDirectory); err != nil {
1965
+ return err
1966
+ }
1967
+ }
1968
+ if cfg.IsIAMServiceTokenIssuer() {
1969
+ if err := validateReferenceConfig("ONEX_IAM_SERVICE_TOKEN_PRIVATE_KEY_PATH", signing.PrivateKeyPath); err != nil {
1970
+ return err
1971
+ }
1972
+ }
1973
+ for name, identity := range internal.OutboundIdentities {
1974
+ if !internalServiceIdentityNamePattern.MatchString(name) {
1975
+ return fmt.Errorf("internal_service.outbound_identities contains invalid name %q", name)
1976
+ }
1977
+ fieldPrefix := fmt.Sprintf("internal_service.outbound_identities[%q]", name)
1978
+ if err := validateReferenceConfig(fieldPrefix+".credential_ref", identity.CredentialRef); err != nil {
1979
+ return err
1980
+ }
1981
+ if cfg.IsProduction() && isLocalDevReference(identity.CredentialRef) {
1982
+ return fmt.Errorf("required config %s.credential_ref must not use a localdev reference in production", fieldPrefix)
1983
+ }
1984
+ if len(identity.AllowedProductCodes) == 0 || len(identity.AllowedProductCodes) > 64 {
1985
+ return fmt.Errorf("required config %s.allowed_product_codes is invalid", fieldPrefix)
1986
+ }
1987
+ seenProducts := make(map[string]struct{}, len(identity.AllowedProductCodes))
1988
+ for _, productCode := range identity.AllowedProductCodes {
1989
+ if !adminExperienceProductCodePattern.MatchString(productCode) {
1990
+ return fmt.Errorf("required config %s.allowed_product_codes is invalid", fieldPrefix)
1991
+ }
1992
+ if _, duplicate := seenProducts[productCode]; duplicate {
1993
+ return fmt.Errorf("required config %s.allowed_product_codes contains duplicate product", fieldPrefix)
1994
+ }
1995
+ seenProducts[productCode] = struct{}{}
1996
+ }
1997
+ if err := serviceidentity.ValidateAudience(identity.Audience); err != nil {
1998
+ return fmt.Errorf("required config %s.audience is invalid", fieldPrefix)
1999
+ }
2000
+ if expected := strings.TrimSpace(identity.ExpectedServiceAccountCode); expected != identity.ExpectedServiceAccountCode ||
2001
+ !serviceAccountCodePattern.MatchString(expected) {
2002
+ return fmt.Errorf("config %s.expected_service_account_code is invalid", fieldPrefix)
2003
+ }
2004
+ }
2005
+ return nil
2006
+ }
2007
+
2008
+ func validateSecretConfig(name, value string, minLength int) error {
2009
+ trimmed := strings.TrimSpace(value)
2010
+ if trimmed == "" {
2011
+ return fmt.Errorf("required config %s is not set", name)
2012
+ }
2013
+ if len(trimmed) < minLength {
2014
+ return fmt.Errorf("required config %s must be at least %d characters", name, minLength)
2015
+ }
2016
+ if containsTemplateFragment(trimmed) {
2017
+ return fmt.Errorf("required config %s must not use a template value", name)
2018
+ }
2019
+ return nil
2020
+ }
2021
+
2022
+ func validateReferenceConfig(name, value string) error {
2023
+ trimmed := strings.TrimSpace(value)
2024
+ if trimmed == "" {
2025
+ return fmt.Errorf("required config %s is not set", name)
2026
+ }
2027
+ if strings.ContainsFunc(trimmed, unicode.IsSpace) {
2028
+ return fmt.Errorf("required config %s must not contain whitespace", name)
2029
+ }
2030
+ if containsTemplateFragment(trimmed) {
2031
+ return fmt.Errorf("required config %s must not use a template value", name)
2032
+ }
2033
+ return nil
2034
+ }
2035
+
2036
+ func containsTemplateFragment(value string) bool {
2037
+ lower := strings.ToLower(strings.TrimSpace(value))
2038
+ templateFragments := []string{"change-me", "your-", "replace-with", "example"}
2039
+ for _, fragment := range templateFragments {
2040
+ if strings.Contains(lower, fragment) {
2041
+ return true
2042
+ }
2043
+ }
2044
+ return false
2045
+ }
2046
+
2047
+ func isLocalDevReference(value string) bool {
2048
+ return strings.HasPrefix(strings.ToLower(strings.TrimSpace(value)), "localdev://")
2049
+ }
2050
+
2051
+ func validateTemporalWorkerConfig(cfg TemporalWorkerConfig) error {
2052
+ return validateTemporalWorkerConfigWithPrefix(cfg, "TEMPORAL_WORKER")
2053
+ }
2054
+
2055
+ func validateTemporalWorkerConfigWithPrefix(cfg TemporalWorkerConfig, envPrefix string) error {
2056
+ if cfg.MaxConcurrentActivities < 0 {
2057
+ return fmt.Errorf("invalid %s_MAX_CONCURRENT_ACTIVITIES: %d, must be greater than or equal to 0", envPrefix, cfg.MaxConcurrentActivities)
2058
+ }
2059
+ if cfg.MaxConcurrentWorkflowTasks < 0 || cfg.MaxConcurrentWorkflowTasks == 1 {
2060
+ return fmt.Errorf("invalid %s_MAX_CONCURRENT_WORKFLOW_TASKS: %d, must be 0 or greater than 1", envPrefix, cfg.MaxConcurrentWorkflowTasks)
2061
+ }
2062
+ if cfg.MaxConcurrentActivityTaskPollers < 0 {
2063
+ return fmt.Errorf("invalid %s_MAX_CONCURRENT_ACTIVITY_TASK_POLLERS: %d, must be greater than or equal to 0", envPrefix, cfg.MaxConcurrentActivityTaskPollers)
2064
+ }
2065
+ if cfg.MaxConcurrentWorkflowTaskPollers < 0 || cfg.MaxConcurrentWorkflowTaskPollers == 1 {
2066
+ return fmt.Errorf("invalid %s_MAX_CONCURRENT_WORKFLOW_TASK_POLLERS: %d, must be 0 or greater than 1", envPrefix, cfg.MaxConcurrentWorkflowTaskPollers)
2067
+ }
2068
+ return nil
2069
+ }