@seedgrid/ai-toolkit 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -2
  3. package/dist/adapters/claude.js +114 -0
  4. package/dist/adapters/codex.js +108 -0
  5. package/dist/adapters/index.js +17 -0
  6. package/dist/adapters/shared.js +142 -0
  7. package/dist/adapters/types.js +1 -0
  8. package/dist/cli.js +203 -0
  9. package/dist/commands/common.js +98 -0
  10. package/dist/commands/doctor.js +290 -0
  11. package/dist/commands/init.js +9 -0
  12. package/dist/commands/install.js +107 -0
  13. package/dist/commands/remove.js +54 -0
  14. package/dist/commands/review.js +127 -0
  15. package/dist/commands/status.js +73 -0
  16. package/dist/commands/update.js +8 -0
  17. package/dist/commands/validate.js +27 -0
  18. package/dist/core/apply.js +181 -0
  19. package/dist/core/blocks.js +64 -0
  20. package/dist/core/content.js +528 -0
  21. package/dist/core/env.js +40 -0
  22. package/dist/core/frontmatter.js +29 -0
  23. package/dist/core/hash.js +14 -0
  24. package/dist/core/inspect.js +51 -0
  25. package/dist/core/lock.js +124 -0
  26. package/dist/core/paths.js +65 -0
  27. package/dist/core/plan.js +242 -0
  28. package/dist/core/tools.js +62 -0
  29. package/dist/core/types.js +11 -0
  30. package/dist/core/updates.js +121 -0
  31. package/docs/arquitetura.md +80 -0
  32. package/docs/compatibilidade.md +50 -0
  33. package/docs/formato.md +155 -0
  34. package/docs/instalacao.md +105 -0
  35. package/docs/manutencao.md +115 -0
  36. package/package.json +43 -4
  37. package/seedgridKB/SeedGrid/Extensions/Catalogo-de-enums.md +161 -0
  38. package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -0
  39. package/seedgridKB/SeedGrid/Extensions/Indice.md +63 -0
  40. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +124 -0
  41. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Enums.md +40 -0
  42. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +97 -0
  43. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +114 -0
  44. package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao-Rust.md +69 -0
  45. package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao.md +88 -0
  46. package/seedgridKB/SeedGrid/Extensions/apikeys/Enums.md +33 -0
  47. package/seedgridKB/SeedGrid/Extensions/apikeys/Exemplos.md +121 -0
  48. package/seedgridKB/SeedGrid/Extensions/apikeys/Rust.md +126 -0
  49. package/seedgridKB/SeedGrid/Extensions/apikeys/Visao-geral.md +111 -0
  50. package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -0
  51. package/seedgridKB/SeedGrid/Extensions/audit/Configuracao.md +127 -0
  52. package/seedgridKB/SeedGrid/Extensions/audit/Enums.md +66 -0
  53. package/seedgridKB/SeedGrid/Extensions/audit/Exemplos.md +151 -0
  54. package/seedgridKB/SeedGrid/Extensions/audit/Rust.md +120 -0
  55. package/seedgridKB/SeedGrid/Extensions/audit/Visao-geral.md +114 -0
  56. package/seedgridKB/SeedGrid/Extensions/backup/Configuracao-Rust.md +136 -0
  57. package/seedgridKB/SeedGrid/Extensions/backup/Configuracao.md +319 -0
  58. package/seedgridKB/SeedGrid/Extensions/backup/Enums.md +131 -0
  59. package/seedgridKB/SeedGrid/Extensions/backup/Exemplos.md +210 -0
  60. package/seedgridKB/SeedGrid/Extensions/backup/Rust.md +135 -0
  61. package/seedgridKB/SeedGrid/Extensions/backup/Visao-geral.md +150 -0
  62. package/seedgridKB/SeedGrid/Extensions/billing/Configuracao-Rust.md +86 -0
  63. package/seedgridKB/SeedGrid/Extensions/billing/Configuracao.md +243 -0
  64. package/seedgridKB/SeedGrid/Extensions/billing/Enums.md +165 -0
  65. package/seedgridKB/SeedGrid/Extensions/billing/Exemplos.md +205 -0
  66. package/seedgridKB/SeedGrid/Extensions/billing/Rust.md +109 -0
  67. package/seedgridKB/SeedGrid/Extensions/billing/Visao-geral.md +181 -0
  68. package/seedgridKB/SeedGrid/Extensions/broker/Configuracao-Rust.md +40 -0
  69. package/seedgridKB/SeedGrid/Extensions/broker/Visao-geral.md +114 -0
  70. package/seedgridKB/SeedGrid/Extensions/cache/Configuracao-Rust.md +73 -0
  71. package/seedgridKB/SeedGrid/Extensions/cache/Configuracao.md +112 -0
  72. package/seedgridKB/SeedGrid/Extensions/cache/Enums.md +36 -0
  73. package/seedgridKB/SeedGrid/Extensions/cache/Exemplos.md +149 -0
  74. package/seedgridKB/SeedGrid/Extensions/cache/Rust.md +81 -0
  75. package/seedgridKB/SeedGrid/Extensions/cache/Visao-geral.md +90 -0
  76. package/seedgridKB/SeedGrid/Extensions/commons/Configuracao.md +107 -0
  77. package/seedgridKB/SeedGrid/Extensions/commons/Enums.md +105 -0
  78. package/seedgridKB/SeedGrid/Extensions/commons/Exemplos.md +141 -0
  79. package/seedgridKB/SeedGrid/Extensions/commons/Visao-geral.md +99 -0
  80. package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +122 -0
  81. package/seedgridKB/SeedGrid/Extensions/exchange/Enums.md +29 -0
  82. package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +82 -0
  83. package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +118 -0
  84. package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao-Rust.md +110 -0
  85. package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao.md +157 -0
  86. package/seedgridKB/SeedGrid/Extensions/filemanagement/Exemplos.md +157 -0
  87. package/seedgridKB/SeedGrid/Extensions/filemanagement/Rust.md +70 -0
  88. package/seedgridKB/SeedGrid/Extensions/filemanagement/Visao-geral.md +88 -0
  89. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao-Rust.md +124 -0
  90. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +195 -0
  91. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Enums.md +71 -0
  92. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Exemplos.md +149 -0
  93. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Rust.md +139 -0
  94. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +130 -0
  95. package/seedgridKB/SeedGrid/Extensions/notification/Configuracao.md +141 -0
  96. package/seedgridKB/SeedGrid/Extensions/notification/Enums.md +52 -0
  97. package/seedgridKB/SeedGrid/Extensions/notification/Exemplos.md +141 -0
  98. package/seedgridKB/SeedGrid/Extensions/notification/Visao-geral.md +113 -0
  99. package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao-Rust.md +67 -0
  100. package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao.md +94 -0
  101. package/seedgridKB/SeedGrid/Extensions/privacy/Enums.md +79 -0
  102. package/seedgridKB/SeedGrid/Extensions/privacy/Exemplos.md +172 -0
  103. package/seedgridKB/SeedGrid/Extensions/privacy/Rust.md +107 -0
  104. package/seedgridKB/SeedGrid/Extensions/privacy/Visao-geral.md +90 -0
  105. package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao-Rust.md +72 -0
  106. package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao.md +104 -0
  107. package/seedgridKB/SeedGrid/Extensions/realtime/Exemplos.md +104 -0
  108. package/seedgridKB/SeedGrid/Extensions/realtime/Rust.md +69 -0
  109. package/seedgridKB/SeedGrid/Extensions/realtime/Visao-geral.md +105 -0
  110. package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao-Rust.md +68 -0
  111. package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -0
  112. package/seedgridKB/SeedGrid/Extensions/resilience/Enums.md +96 -0
  113. package/seedgridKB/SeedGrid/Extensions/resilience/Exemplos.md +229 -0
  114. package/seedgridKB/SeedGrid/Extensions/resilience/Rust.md +80 -0
  115. package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -0
  116. package/seedgridKB/SeedGrid/Extensions/runtime/Configuracao-Rust.md +90 -0
  117. package/seedgridKB/SeedGrid/Extensions/runtime/Visao-geral.md +114 -0
  118. package/seedgridKB/SeedGrid/Extensions/security/Configuracao-Rust.md +327 -0
  119. package/seedgridKB/SeedGrid/Extensions/security/Configuracao.md +510 -0
  120. package/seedgridKB/SeedGrid/Extensions/security/Enums.md +95 -0
  121. package/seedgridKB/SeedGrid/Extensions/security/Exemplos.md +218 -0
  122. package/seedgridKB/SeedGrid/Extensions/security/Rust.md +203 -0
  123. package/seedgridKB/SeedGrid/Extensions/security/Visao-geral.md +131 -0
  124. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust.md +188 -0
  125. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +345 -0
  126. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Enums.md +117 -0
  127. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Exemplos.md +205 -0
  128. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Rust.md +161 -0
  129. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +142 -0
  130. package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao-Rust.md +75 -0
  131. package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao.md +152 -0
  132. package/seedgridKB/SeedGrid/Extensions/whatsapp/Enums.md +198 -0
  133. package/seedgridKB/SeedGrid/Extensions/whatsapp/Exemplos.md +173 -0
  134. package/seedgridKB/SeedGrid/Extensions/whatsapp/Rust.md +83 -0
  135. package/seedgridKB/SeedGrid/Extensions/whatsapp/Visao-geral.md +99 -0
  136. package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +112 -0
  137. package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -0
  138. package/seedgridKB/SeedGrid/SeedGrid-components/buttons/API.md +257 -0
  139. package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Exemplos.md +307 -0
  140. package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Visao-geral.md +51 -0
  141. package/seedgridKB/SeedGrid/SeedGrid-components/datatable/API.md +174 -0
  142. package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Exemplos.md +236 -0
  143. package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Visao-geral.md +76 -0
  144. package/seedgridKB/SeedGrid/SeedGrid-components/digits/API.md +339 -0
  145. package/seedgridKB/SeedGrid/SeedGrid-components/digits/Exemplos.md +311 -0
  146. package/seedgridKB/SeedGrid/SeedGrid-components/digits/Visao-geral.md +57 -0
  147. package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +125 -0
  148. package/seedgridKB/SeedGrid/SeedGrid-components/editor/Exemplos.md +116 -0
  149. package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -0
  150. package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/API.md +490 -0
  151. package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Exemplos.md +544 -0
  152. package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Visao-geral.md +69 -0
  153. package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/API.md +113 -0
  154. package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Exemplos.md +159 -0
  155. package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Visao-geral.md +92 -0
  156. package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/API.md +516 -0
  157. package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -0
  158. package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Visao-geral.md +80 -0
  159. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/API.md +615 -0
  160. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Exemplos.md +384 -0
  161. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Visao-geral.md +111 -0
  162. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/API.md +661 -0
  163. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Exemplos.md +312 -0
  164. package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -0
  165. package/seedgridKB/SeedGrid/SeedGrid-components/layout/API.md +1090 -0
  166. package/seedgridKB/SeedGrid/SeedGrid-components/layout/Exemplos.md +1172 -0
  167. package/seedgridKB/SeedGrid/SeedGrid-components/layout/Visao-geral.md +98 -0
  168. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/Visao-geral.md +98 -0
  169. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-apikeys.md +118 -0
  170. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-audit.md +88 -0
  171. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-backup.md +131 -0
  172. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-platform.md +132 -0
  173. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-user.md +120 -0
  174. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-login-tenancy.md +119 -0
  175. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-security.md +427 -0
  176. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-self-enter.md +104 -0
  177. package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-subdomain-tenancy.md +110 -0
  178. package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/API.md +490 -0
  179. package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +561 -0
  180. package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Visao-geral.md +82 -0
  181. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/Visao-geral.md +68 -0
  182. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-commons.md +61 -0
  183. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-core.md +140 -0
  184. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-playground.md +112 -0
  185. package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-theme.md +97 -0
  186. package/seedgridKB/SeedGrid/SeedGrid-components/providers/API.md +177 -0
  187. package/seedgridKB/SeedGrid/SeedGrid-components/providers/Exemplos.md +332 -0
  188. package/seedgridKB/SeedGrid/SeedGrid-components/providers/Visao-geral.md +106 -0
  189. package/seedgridKB/SeedGrid/SeedGrid-components/showcase.md +70 -0
  190. package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +53 -0
  191. package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +45 -0
  192. package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +43 -0
  193. package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +41 -0
  194. package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +44 -0
  195. package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +40 -0
  196. package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +78 -0
  197. package/seedgridKB/SeedGrid/Toolkit/references/glossario.md +15 -0
  198. package/seedgridKB/SeedGrid/Toolkit/references/modos-shared-native.md +27 -0
  199. package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +54 -0
  200. package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +39 -0
  201. package/seedgridKB/SeedGrid/Toolkit/rules/formato-entrega.md +29 -0
  202. package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +47 -0
  203. package/seedgridKB/SeedGrid/Toolkit/rules/personalizacao-local.md +22 -0
  204. package/seedgridKB/SeedGrid/Toolkit/skills/planejar-mudanca/SKILL.md +38 -0
  205. package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/SKILL.md +33 -0
  206. package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/references/checklist.md +25 -0
  207. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-backend-crud/SKILL.md +29 -0
  208. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-bump/SKILL.md +26 -0
  209. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/SKILL.md +221 -0
  210. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/agents/openai.yaml +4 -0
  211. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/checklist.md +31 -0
  212. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/patterns.md +185 -0
  213. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/routing.md +66 -0
  214. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/SKILL.md +56 -0
  215. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +4 -0
  216. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/checklist.md +32 -0
  217. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/steps.md +114 -0
  218. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-review/SKILL.md +61 -0
  219. package/seedgridKB/SeedGrid/Toolkit/skills/validar-mudanca/SKILL.md +29 -0
  220. package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +29 -0
  221. package/seedgridKB/SeedGrid/Toolkit/templates/especificacao-tarefa.md +32 -0
  222. package/seedgridKB/SeedGrid/Toolkit/templates/relatorio-validacao.md +30 -0
  223. package/seedgridKB/SeedGrid/Versoes.md +64 -0
  224. package/seedgridKB/SeedGrid/Visao-geral.md +35 -0
  225. package/seedgridKB/manifest.json +142 -0
@@ -0,0 +1,205 @@
1
+ ---
2
+ tags: [seedgrid, extension, subdomain-tenancy, exemplos]
3
+ fonte: java/seedgrid-quarkus-ext-subdomain-tenancy, java/examples/seedgrid-subdomain-tenancy-example
4
+ branch: main
5
+ commit: fe62530a29
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # subdomain-tenancy — Exemplos (Quarkus)
10
+
11
+ Visão geral: [[SeedGrid/Extensions/subdomain-tenancy/Visao-geral|Visão geral do subdomain-tenancy]] · Catálogo de propriedades: [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração do subdomain-tenancy]] · [[SeedGrid/Extensions/subdomain-tenancy/Rust|Porte Rust]]
12
+
13
+ Fontes: app de exemplo `java/examples/seedgrid-subdomain-tenancy-example` (repositório git próprio, commit `cd83edd` — fora da tabela de commits da análise), `README.md` da extension e o código do runtime (commit `fe62530a29`). Outro consumidor no monorepo: `java/seedgrid-test-multitenancy`.
14
+
15
+ ## 1. Dependência
16
+
17
+ ```xml
18
+ <dependency>
19
+ <groupId>br.com.seedgrid</groupId>
20
+ <artifactId>seedgrid-subdomain-tenancy</artifactId>
21
+ <version>2026.10.14-1</version>
22
+ </dependency>
23
+ ```
24
+
25
+ O exemplo usa a propriedade `seedgrid.subdomain-tenancy.version` (`2026.10.13` no pom dele) e importa o parent da extension como BOM para não pinar commons/security/billing… à mão (comentário no `pom.xml` do exemplo).
26
+
27
+ ## 2. Configuração do host (trecho real do exemplo)
28
+
29
+ `java/examples/seedgrid-subdomain-tenancy-example/src/main/resources/application.properties` — segredos substituídos por placeholders e só com as chaves que têm efeito:
30
+
31
+ ```properties
32
+ # Entidades do app somadas às da lib (reaproveita as chaves da extension)
33
+ quarkus.hibernate-orm.packages=${seedgrid.subdomain-tenancy.tenant.entity-packages},br.com.seedgrid.security.entities,br.com.seedgrid.backup.entities,br.com.seedgrid.apikeys.entities
34
+ quarkus.hibernate-orm.master.packages=${seedgrid.subdomain-tenancy.master.entity-packages},br.com.seedgrid.subdomaintenancy.entities
35
+
36
+ seedgrid.subdomain-tenancy.product-prefix=${PRODUCT_PREFIX:kb}
37
+
38
+ # Ponte do datasource dos TENANTS com env vars curtas (criadas pelo exemplo, não pela lib)
39
+ seedgrid.subdomain-tenancy.tenant.datasource.jdbc.url=${SEEDGRID_SUBDOMAIN_TENANCY_TENANT_DB_URL:jdbc:postgresql://localhost:5439}
40
+ seedgrid.subdomain-tenancy.tenant.datasource.username=${SEEDGRID_SUBDOMAIN_TENANCY_TENANT_DB_USERNAME:postgres}
41
+ seedgrid.subdomain-tenancy.tenant.datasource.password=<SEGREDO:tenant-db-password>
42
+
43
+ # Bootstrap de startup do security desligado (pressupõe banco único)
44
+ seedgrid.security.bootstrap.enabled=false
45
+ ```
46
+
47
+ Observação (**Confirmado** no código da extension): `domain-suffix`, `master.datasource.*`, `mailer.*`, `admin-user.*`, `cleanup.*` vêm do `microprofile-config.properties` da extension e são ajustadas por env vars `SEEDGRID_SUBDOMAIN_TENANCY_*` (lista em [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração]]).
48
+
49
+ ## 3. Entidades e repositórios: master × tenant
50
+
51
+ PU default = banco do tenant (resolvido pelo host). PU `master` = banco global. Exemplo real (`repository/PaymentPlanRepository.java`):
52
+
53
+ ```java
54
+ @ApplicationScoped
55
+ @PersistenceUnit("master")
56
+ public class PaymentPlanRepository implements PanacheRepository<PaymentPlan> {
57
+
58
+ public List<PaymentPlan> findByDescription(String description) {
59
+ return list("LOWER(description) LIKE LOWER(?1)", "%" + description + "%");
60
+ }
61
+ }
62
+ ```
63
+
64
+ O `PersonRepository` (entidade `entity/tenant/Person`) não usa `@PersistenceUnit` e, portanto, opera no banco do tenant corrente.
65
+
66
+ ## 4. Recurso visível só em tenants (`@TenantOnly`)
67
+
68
+ Trecho real de `resource/PersonResource.java`:
69
+
70
+ ```java
71
+ @Path("/api/persons")
72
+ @Produces(MediaType.APPLICATION_JSON)
73
+ @Consumes(MediaType.APPLICATION_JSON)
74
+ @Tag(name = "Persons", description = "Person management (TENANT)")
75
+ @Tag(name = "tenant-only", description = "Endpoints disponíveis apenas no contexto de tenant")
76
+ @SecurityRequirement(name = "bearerAuth")
77
+ @TenantOnly
78
+ @Blocking
79
+ public class PersonResource {
80
+
81
+ final PersonService service;
82
+ public PersonResource(PersonService service) {
83
+ this.service = service;
84
+ }
85
+
86
+ @GET
87
+ @Operation(summary = "List all persons")
88
+ @Tag(name = "tenant-only")
89
+ public List<PersonDTO> listAll() {
90
+ return service.findAll();
91
+ }
92
+ }
93
+ ```
94
+
95
+ `@TenantOnly`/`@MasterOnly` só afetam o spec servido em `GET /public/tenant-openapi`; não bloqueiam acesso. O exemplo repete `@Tag(name = "tenant-only")` nos métodos — o filtro (`TenantOpenApiController#copyIfAllowed`) trabalha por operação, então a repetição garante que a tag chegue em cada operação do spec (**Inferência**: não foi verificado se a meta-anotação na classe é propagada pelo SmallRye OpenAPI).
96
+
97
+ ## 5. Sobrescrevendo uma SPI (PasswordEncoder)
98
+
99
+ Exemplo real (`multitenancy/CustomPasswordEncoder.java`). Um bean normal vence o `BCryptPasswordEncoder` `@DefaultBean` da extension:
100
+
101
+ ```java
102
+ @ApplicationScoped
103
+ public class CustomPasswordEncoder implements PasswordEncoder {
104
+
105
+ private static final int SALT_ROUNDS = 12;
106
+
107
+ @Override
108
+ public String encode(String plainPassword) {
109
+ if (plainPassword == null) {
110
+ throw new IllegalArgumentException("Password cannot be null");
111
+ }
112
+ return BCrypt.hashpw(plainPassword, BCrypt.gensalt(SALT_ROUNDS));
113
+ }
114
+ }
115
+ ```
116
+
117
+ ## 6. Canal próprio para o OTP (SubdomainTenancyNotificationService)
118
+
119
+ Assinaturas reais: `void sendValidationNotification(TenantValidationNotification notification)`; o record tem `recipient`, `recipientName`, `tradeName`, `validationCode`, `expiryMinutes`, `subject`, `htmlContent`, `channelHint` (o `TenantService` envia `channelHint="email"`). Esboço (o corpo é do host):
120
+
121
+ ```java
122
+ @ApplicationScoped
123
+ public class WhatsAppValidationNotifier implements SubdomainTenancyNotificationService {
124
+
125
+ @Override
126
+ public void sendValidationNotification(TenantValidationNotification n) {
127
+ // enviar n.validationCode() para n.recipient(), válido por n.expiryMinutes() minutos
128
+ }
129
+ }
130
+ ```
131
+
132
+ Por ser bean normal, substitui o `MailerNotificationService` (`@DefaultBean`). Se `sendValidationNotification` lançar `RuntimeException`, o `TenantService` não desfaz o cadastro: a resposta do signup vem com `emailDelivered=false`.
133
+
134
+ Para só trocar o HTML do e-mail padrão, coloque um template Qute em `src/main/resources/templates/subdomain-tenancy/confirmation.html` (ou `subdomain-tenancy/html/confirmation.html`) — o `DefaultTemplateSubdomainTenancyProvider` tenta esses caminhos antes do template embutido. Os dados passados ao template não foram detalhados aqui (ver `spi/impl/DefaultTemplateSubdomainTenancyProvider`).
135
+
136
+ ## 7. Migrations do host
137
+
138
+ Estrutura real do exemplo:
139
+
140
+ ```text
141
+ src/main/resources/db/migration/
142
+ ├── master/ V2000__create_payment_plans_table.sql, V2001__seed_payment_plans_permissions.sql, V2002__create_role_teste_apikey.sql
143
+ └── tenant/ V1__create_persons_table.sql
144
+ ```
145
+
146
+ `master/` roda no boot (history `flyway_schema_history_master`); `tenant/` roda no provisionamento e nas migrações por tenant (history `flyway_schema_history_tenant`). Para pastas extras use `seedgrid.subdomain-tenancy.{master,tenant}.migrations.location.additional` com versões que não colidam com a base.
147
+
148
+ ## 8. Ciclo de vida de um tenant (HTTP)
149
+
150
+ Body conforme `TenantSignupRequest` (`subdomain` `^[a-z0-9_]{5,50}$`, `cnpj` com `@CNPJ`, `rootEmail` `@Email`, representante legal com nome/sobrenome/telefone obrigatórios). Fonte: README da extension, conferido com o DTO.
151
+
152
+ ```bash
153
+ # 1) signup (público, 100/min por IP)
154
+ curl -X POST https://crm.example.com/public/tenants \
155
+ -H "Content-Type: application/json" \
156
+ -d '{
157
+ "subdomain": "clientea",
158
+ "cnpj": "<cnpj>",
159
+ "corporateName": "Cliente A Ltda",
160
+ "tradeName": "Cliente A",
161
+ "rootEmail": "admin@clientea.example",
162
+ "plainRootPassword": "<SEGREDO:senha-root>",
163
+ "address": { "street": "Rua X", "number": "100", "city": "São Paulo", "state": "SP", "postalCode": "01000-000", "district": "Centro" },
164
+ "legalRepresentative": { "firstName": "Maria", "lastName": "Silva", "cpf": "<cpf>", "birthDate": "1990-01-01", "phone": "11999990000" }
165
+ }'
166
+ # -> 201 {"status":"CREATED","emailDelivered":true,"result":"Email de confirmacao enviado para ..."}
167
+
168
+ # 2) validar OTP (aceita 123456 ou 123-456) -> cria banco crm_clientea, migra, cria root, conta de billing
169
+ curl -X POST https://crm.example.com/public/tenants/validate-email \
170
+ -H "Content-Type: application/json" -d '{"subdomain":"clientea","code":"123-456"}'
171
+
172
+ # 3) reenviar OTP (5/h por IP)
173
+ curl -X POST "https://crm.example.com/public/tenants/resend-email?subdomain=clientea"
174
+
175
+ # 4) a partir daqui, o tenant é resolvido pelo host
176
+ curl https://clientea.crm.example.com/api/persons -H "Authorization: Bearer <token>"
177
+ ```
178
+
179
+ > Atenção: com `domain-suffix=crm.example.com`, um subdomínio com `_` passa no signup mas não é extraído do host (o extractor aceita só `[a-z0-9-]`). Prefira nomes sem `_`.
180
+
181
+ ## 9. Operação (contexto master)
182
+
183
+ ```bash
184
+ # Forçar migração de um tenant
185
+ curl -X POST "https://crm.example.com/internal/migrations/tenants/clientea/trigger?force=true" -H "Authorization: Bearer <token-master>"
186
+ # Estado da migração
187
+ curl https://crm.example.com/internal/migrations/tenants/clientea/status -H "Authorization: Bearer <token-master>"
188
+ # Health do tenant (status HTTP derivado do código: OK, TENANT_INACTIVE, DATABASE_UNREACHABLE, MIGRATION_*...)
189
+ curl https://crm.example.com/internal/health/tenants/clientea -H "Authorization: Bearer <token-master>"
190
+ ```
191
+
192
+ Chamados a partir de um host de tenant, esses endpoints respondem `403 {"error":"master_scope_required"}`. Durante uma migração disparada pelo login, `POST /auth/login` no tenant recebe `503` com `Retry-After: 15` e `{"error":"tenant_migration_in_progress", ...}`.
193
+
194
+ ## 10. Lendo o tenant corrente no código do host
195
+
196
+ `TenantContext` é `@RequestScoped` e preenchido pelo `TenantResolutionFilter`:
197
+
198
+ ```java
199
+ @Inject
200
+ TenantContext tenantContext;
201
+
202
+ String tenant = tenantContext.getCurrentTenant(); // "master" quando o host não traz subdomínio
203
+ ```
204
+
205
+ O `QuarkusTenantResolver` dá prioridade ao valor já gravado no `TenantContext`; portanto `setCurrentTenant("outro")` dentro da request muda o banco usado pelas **próximas** sessões Hibernate da PU default (**Inferência** a partir de `QuarkusTenantResolver#resolveTenantId`; não há exemplo/teste do host fazendo isso). `TenantScopedExecutor#call(Supplier<T>)` apenas abre uma transação `REQUIRES_NEW`.
@@ -0,0 +1,161 @@
1
+ ---
2
+ tags: [seedgrid, extension, subdomain-tenancy, rust]
3
+ fonte: rust/seedgrid-rust-crate-subdomain-tenancy
4
+ branch: main
5
+ commit: be738a2d1e
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # `seedgrid-rust-crate-subdomain-tenancy` — porte Rust
10
+
11
+ Lado Java: [[SeedGrid/Extensions/subdomain-tenancy/Visao-geral|Visão geral do subdomain-tenancy (Quarkus)]] · [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração Java]]
12
+ Configuração deste crate: [[SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust|Configuração Rust do subdomain-tenancy]]
13
+
14
+ ## Objetivo
15
+
16
+ Porte da extensão `seedgrid-quarkus-ext-subdomain-tenancy` (antiga *multitenancy*): multitenancy **por subdomínio**, modelo **SILO** (um banco Postgres por tenant). Principais capacidades (Confirmado em `README.md`, `src/lib.rs`, `src/orchestrator.rs`):
17
+
18
+ - Resolução do tenant pelo host `{tenant}.{domain-suffix}` (prioriza `X-Forwarded-Host`; fallback `master`).
19
+ - Registro de tenants no banco **master** (`tenants`, `tenant_validation_tokens`, `tenant_migration_state`).
20
+ - Signup público de tenant com OTP por e-mail, validação e **provisionamento** (`CREATE DATABASE` + migrations security/tenant/audit/backup/apikeys + admin root).
21
+ - **Coordenação de migrations por tenant** (lock com lease/fencing token, heartbeat, concorrência limitada) e **gate de login** enquanto o tenant migra.
22
+ - Pools por tenant com LRU (`max-active-datasources`) e circuit breaker; middleware que devolve 404 para tenant inexistente/inativo.
23
+ - Schedulers de cleanup de tenants não validados e de trial.
24
+ - Orquestrador `assemble_all`: monta tenancy + security + billing + audit + backup, injeta os hooks de tenancy no security e devolve `Router` e OpenAPI agregados.
25
+ - PII at-rest do `Tenant` via `CryptoProvider` global do `seedgrid-privacy`; auditoria de criação de tenant.
26
+
27
+ ## Coordenadas
28
+
29
+ | Item | Valor |
30
+ |---|---|
31
+ | Diretório / pacote / lib | `rust/seedgrid-rust-crate-subdomain-tenancy` / `seedgrid-rust-crate-subdomain-tenancy` / `seedgrid_subdomain_tenancy` |
32
+ | Versão | `version.workspace = true` → `2026.10.14` |
33
+ | Features | `default = ["postgres"]`, `postgres = ["dep:sqlx"]` |
34
+ | Reexporta | `seedgrid_apikeys`, `seedgrid_audit`, `seedgrid_backup`, `seedgrid_billing`, `seedgrid_cache`, `seedgrid_privacy`, `seedgrid_resilience`, `seedgrid_security` (usa também `seedgrid_runtime::events`) |
35
+
36
+ ## API pública (principais)
37
+
38
+ | Símbolo | Papel |
39
+ |---|---|
40
+ | `SubdomainTenancyConfig` (+ `from_env`, `apply_datasource_env`) | ver [[SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust\|Configuração Rust]] |
41
+ | `assemble_all(Arc<SubdomainTenancyConfig>, Option<PgPool>, PlatformOverrides)` → `AssembledPlatform { tenancy, security, billing, audit_scoped, backup_scoped, router, openapi }` | "CDI" do host |
42
+ | `TenancyPlatform::assemble(...)` → `AssembledTenancy` (`provisioning_router()`, `shutdown_schedulers()`), `HostOverrides` | montagem só da tenancy |
43
+ | `TenantService`, `TenantProvisioner`, `TenantProvisioningService`, `TenantRepository`, `TenantValidationTokenRepository` | signup/validação/provisionamento |
44
+ | `TenantMigrationCoordinator` (em `migration`), `PgTenantMigrationStore`, `DefaultTenantSchemaMigrator`, `TenantBusinessSchemaMigrator` | migração por tenant |
45
+ | `TenantPoolProvider`, `TenantConnectionResolver`, `TenantScoped`/`TenantStateFactory`, `TenantContext` | roteamento de pools/estado por tenant |
46
+ | `tenant_migration_gate` / `TenantMigrationGateState`, `tenant_middleware::*` | middlewares |
47
+ | `TenantJwtTokenCustomizer`, `TenantScopeResolver` (`spi`) | hooks para o security |
48
+ | `TenantHealthService`, `TenantHealthReport` | health por tenant |
49
+ | `extract_tenant_from_host`, `resolve_host_header`, `resolve_tenant` | resolução |
50
+ | `spawn_backup_scheduler`, `schedulers::*` | jobs |
51
+ | `migrate(&PgPool)` / `migrate_startup(...)` / `MIGRATOR` | migrations do master `0100..0109` |
52
+ | `router(SubdomainTenancyState)`, `provisioning_router(TenantProvisioningApiState)`, `openapi()` | rotas |
53
+
54
+ ## Endpoints (Confirmado em `src/routes.rs` e `src/provisioning_routes.rs`)
55
+
56
+ | Método | Path |
57
+ |---|---|
58
+ | POST | `/public/tenants` (signup) |
59
+ | POST | `/public/tenants/validate-email` |
60
+ | POST | `/public/tenants/resend-email` |
61
+ | GET | `/internal/health/tenants/{tenantId}` |
62
+ | POST | `/internal/migrations/tenants/{tenantId}/trigger` |
63
+ | GET | `/internal/migrations/tenants/{tenantId}/status` |
64
+ | POST | `/internal/tenants/{tenantId}/provision` (`provisioning_router`) |
65
+ | GET | `/tenants`, `/tenants/{subdomain}` (`provisioning_router`; resumo do registro master) |
66
+
67
+ Rotas restritas ao contexto master respondem 403 `master_scope_required` quando chamadas sob outro tenant (`src/routes.rs`).
68
+
69
+ O `router` agregado do `assemble_all` inclui ainda security, billing, `/audit-logs` e `/exports` (backup), com middleware de resolução de tenant, gate de migração e auth admin via security.
70
+
71
+ ## Exemplos
72
+
73
+ Montagem completa (Inferência a partir das assinaturas de `assemble_all` e `SubdomainTenancyConfig`; não há exemplo no repositório usando `assemble_all`):
74
+
75
+ ```rust
76
+ use std::sync::Arc;
77
+ use seedgrid_subdomain_tenancy::{assemble_all, PlatformOverrides, SubdomainTenancyConfig};
78
+
79
+ async fn boot(master_url: &str) -> Result<(), Box<dyn std::error::Error>> {
80
+ let mut cfg = SubdomainTenancyConfig::from_env();
81
+ cfg.apply_datasource_env(Some(master_url));
82
+ let master_pool = sqlx::PgPool::connect(master_url).await?;
83
+ let assembled = assemble_all(Arc::new(cfg), Some(master_pool), PlatformOverrides::default())
84
+ .await
85
+ .map_err(|e| e.to_string())?;
86
+ let app = axum::Router::new().nest("/api/v1", assembled.router);
87
+ let _ = app;
88
+ Ok(())
89
+ }
90
+ ```
91
+
92
+ Exemplo executável (Confirmado): `rust/examples/seedgrid-subdomain-tenancy-example/src/main.rs` — monta à mão `TenantPoolProvider`, `DefaultTenantSchemaMigrator`, `TenantMigrationCoordinator`, `TenantProvisioningService` e `TenantService` (trial 14 dias, OTP 10 min, `NoopPasswordEncoder` só para dev) e percorre signup → validação → provisionamento. Usa envs próprias do exemplo (`DATABASE_URL`, `TENANT_URL_PATTERN`, `TENANT_DB_USER`, `TENANT_DB_PASSWORD`, `EXAMPLE_TENANT`).
93
+
94
+ ## Migrations (master)
95
+
96
+ `0100_create_tenants_table.sql` … `0109_privacy_search_hashes.sql` (10 arquivos): tenants, root user, trial, snapshot de usuário, `tenant_migration_state` (+ fencing token), índices, `tenant_validation_tokens` (+ base entity), hashes de privacidade. Os bancos de tenant recebem as migrations de security/audit/backup/apikeys + negócio via `DefaultTenantSchemaMigrator`.
97
+
98
+ ## Testes
99
+
100
+ Unitários em `src/**` (config, resolver, gate, service...) e `tests/db_it.rs` (Testcontainers). **Não executados** nesta análise.
101
+
102
+ ## Notas de comportamento
103
+
104
+ - O crate lê `SEEDGRID_MULTITENANCY_*` (o módulo Java usa `SEEDGRID_SUBDOMAIN_TENANCY_*`) — ver [[SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust|Configuração Rust]].
105
+ - Gate de login mais permissivo (`ends_with` e bypass do `default_tenant`).
106
+ - `validate_tenant` provisiona antes de marcar tokens usados.
107
+ - O admin do master é semeado pelo bootstrap do security (envs `SEEDGRID_SECURITY_FIRST_ADMIN_USER_*`), não por `subdomain-tenancy.admin-user.*`.
108
+
109
+ ## Enums
110
+
111
+ Equivalentes Java em [[SeedGrid/Extensions/subdomain-tenancy/Enums|Enums (Quarkus)]].
112
+
113
+ | Enum | Arquivo | Usado em | Equivalente Java |
114
+ |---|---|---|---|
115
+ | `TenantMigrationStatus` | `src/migration/mod.rs` | coluna `tenant_migration_state.status`; corpo de `/internal/migrations/...` | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantMigrationStatus\|TenantMigrationStatus]] |
116
+ | `TenantMigrationDecision` | `src/migration/mod.rs` | `TenantMigrationOutcome`; gate de login | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantMigrationDecision\|TenantMigrationDecision]] |
117
+ | `TenantHealthStatus` | `src/health.rs` | corpo de `GET /internal/health/tenants/{tenantId}` | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantHealthStatus\|TenantHealthStatus]] |
118
+ | `TenantHealthCode` | `src/health.rs` | idem | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantHealthCode\|TenantHealthCode]] |
119
+ | `SignupOutcome` | `src/service.rs` | resposta de `POST /public/tenants` | [[SeedGrid/Extensions/subdomain-tenancy/Enums#SignupOutcome\|SignupOutcome]] |
120
+ | `StateEnum` | `src/models/embeddables.rs` | `state` do endereço do signup e do tenant | `StateEnum` da lib commons |
121
+ | `TenantAccess` | `src/pool_resolver.rs` | resultado do gate de tenant no middleware | sem equivalente (específico do Rust) |
122
+ | `TenantServiceError`, `ProvisioningError`, `AssemblyError` | `service.rs`, `provisioning.rs`, `platform.rs` | erros | exceções do Java |
123
+
124
+ ### TenantMigrationStatus
125
+
126
+ `Pending` → `PENDING`, `Ready` → `READY`, `Migrating` → `MIGRATING`, `Failed` → `FAILED` (via `name()`). Coluna `status VARCHAR(20) NOT NULL DEFAULT 'PENDING'`, sem CHECK (`migrations/0104_create_tenant_migration_state.sql`). `parse(Option<&str>)` é tolerante: apara, ignora caixa e transforma qualquer valor desconhecido ou ausente em `Pending`. Java: mesmos 4 valores na mesma ordem.
127
+
128
+ ### TenantMigrationDecision
129
+
130
+ `Ready` → `READY`, `InProgress` → `IN_PROGRESS`, `Failed` → `FAILED`. Não é persistido. No gate de login, `Ready` deixa passar; qualquer outra decisão responde **503** com `Retry-After: 15` e `error` = `tenant_migration_in_progress` ou `tenant_migration_failed`. Java: mesmos 3 valores.
131
+
132
+ ### TenantHealthStatus e TenantHealthCode
133
+
134
+ - `TenantHealthStatus`: `Up` (`UP`), `Down` (`DOWN`), `OutOfService` (`OUT_OF_SERVICE`).
135
+ - `TenantHealthCode`: `Ok`, `InvalidTenant`, `TenantNotFound`, `TenantInactive`, `DatabaseUnreachable`, `MigrationPending`, `MigrationRunning`, `MigrationFailed`. O `name()` sai em SCREAMING_SNAKE (`OK`, `INVALID_TENANT`, ...).
136
+ - HTTP do health (`resolve_health_status`): `UP` → 200; `INVALID_TENANT` → 400; `TENANT_NOT_FOUND` → 404; os demais → 503.
137
+ - Java: mesmos valores e mesma ordem nos dois enums. Não são persistidos; o corpo JSON usa `name()`.
138
+
139
+ ### SignupOutcome
140
+
141
+ `Created` → `CREATED` e `UpdatedPending` → `UPDATED_PENDING`, com resposta 201 e 200, respectivamente, em `POST /public/tenants`. Java: `CREATED`, `UPDATED_PENDING`, idêntico.
142
+
143
+ ### StateEnum (subdomain-tenancy)
144
+
145
+ 27 UFs com variantes em maiúsculas (`AC` ... `TO`), com `Serialize`/`Deserialize` por derive: serializa e desserializa **somente a sigla exata em maiúsculas**. **Atenção:** o `StateEnum` do login-tenancy Rust também aceita minúsculas e o nome completo do estado (como o `fromJson` do Java); este aqui não.
146
+
147
+ ### TenantAccess
148
+
149
+ `Master` (escopo master, segue no pool master), `Active` (tenant existente e ativo, pool aquecido) e `Rejected` (inexistente ou inativo: o middleware responde 404, fail-closed). Sem equivalente Java.
150
+
151
+ ### Erros
152
+
153
+ - `TenantServiceError`: `SubdomainAlreadyExists`, `CnpjAlreadyExists`, `TenantNotFound`, `TenantAlreadyActive`, `ValidationCodeExpired`, `Db`, `OtpGeneration`, `Provisioning`, `WeakPassword(Vec<String>)`.
154
+ - `ProvisioningError`: `Locked(String)` (lock de provisionamento já tomado) e `Db(String)`.
155
+ - `AssemblyError`: `Migration(MigrateError)`.
156
+
157
+ ### Internos
158
+
159
+ | Enum | Arquivo |
160
+ |---|---|
161
+ | `SchedulerStep` (`Cleanup`, `Queue`, `Notify`) | `src/backup_scheduler.rs` |
@@ -0,0 +1,142 @@
1
+ ---
2
+ tags: [seedgrid, extension, subdomain-tenancy]
3
+ fonte: java/seedgrid-quarkus-ext-subdomain-tenancy
4
+ branch: main
5
+ commit: fe62530a29
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # subdomain-tenancy — Visão geral (Quarkus)
10
+
11
+ Configuração: [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração do subdomain-tenancy]] · Enums: [[SeedGrid/Extensions/subdomain-tenancy/Enums|Enums do subdomain-tenancy]] · Exemplos: [[SeedGrid/Extensions/subdomain-tenancy/Exemplos|Exemplos do subdomain-tenancy]] · [[SeedGrid/Extensions/subdomain-tenancy/Rust|Porte Rust]]
12
+
13
+ > [!note] Fontes
14
+ > Lido no código do commit `fe62530a29`. O `README.md` e os `docs/` da extension foram revisados contra o código em 2026-10-14. Os relatórios `AJUSTES_REALIZADOS.md`, `AUDITORIA_FINAL_CONFORMIDADE.md`, `CORRECOES_FINALIZADAS.md`, `IMPLEMENTATION_SUMMARY.md`, `RELATORIO_CONFORMIDADE.md`, `REVISAO_FINAL_CONFORMIDADE.md` e `SETUP_ENVIRONMENT.md` (raiz do repo) são históricos.
15
+
16
+ ## Objetivo
17
+
18
+ Multi-tenancy **um banco PostgreSQL por tenant**, com o tenant identificado pelo **subdomínio** do host (`cliente1.crm.example.com` → tenant `cliente1`). Inclui:
19
+
20
+ - Resolução de tenant por request (`X-Forwarded-Host`/`Host`) e `TenantResolver`/`TenantConnectionResolver` do Hibernate ORM em modo `DATABASE`.
21
+ - Banco **master** (persistence unit `master`) com o catálogo `tenants`, security, billing, apikeys e afins.
22
+ - **Signup self-service** com validação por OTP de 6 dígitos por e-mail e **provisionamento** automático (CREATE DATABASE + migrations + usuário root).
23
+ - Cache de pools JDBC por tenant com limite, evicção por TTL/LRU e **circuit breaker** por tenant.
24
+ - **Orquestração de migrations por tenant** (sob demanda no login, manual, ou todas no boot) com lock distribuído e heartbeat.
25
+ - Health por tenant, OpenAPI filtrado por contexto (master × tenant), métricas Micrometer e spans OpenTelemetry.
26
+ - Bootstrap do admin global no master, limpeza de cadastros não validados, integração com billing (conta + trial), audit, backup e security.
27
+
28
+ ## Módulos e coordenadas Maven
29
+
30
+ | Módulo | Coordenada | Observação |
31
+ |---|---|---|
32
+ | parent | `br.com.seedgrid:seedgrid-subdomain-tenancy-parent:2026.10.14-1` (pom) | Parent: `br.com.seedgrid:seedgrid-parent:2026.07.02` (Quarkus 3.30.8). Centraliza as versões da foundation em `<properties>` e `dependencyManagement` |
33
+ | runtime | `br.com.seedgrid:seedgrid-subdomain-tenancy:2026.10.14-1` | O que o host declara |
34
+ | deployment | `br.com.seedgrid:seedgrid-subdomain-tenancy-deployment:2026.10.14-1` | `SubdomainTenancyProcessor` |
35
+
36
+ **Confirmado** em `pom.xml`, `runtime/pom.xml`, `deployment/pom.xml`.
37
+
38
+ ### Dependências SeedGrid (versões do parent)
39
+
40
+ | Artefato | Versão | Uso aqui |
41
+ |---|---|---|
42
+ | `seedgrid-commons` | 2026.10.08 | `BaseEntity`, `commons.spi.TenantProvider`, `SchemaUtils`, `ApiExceptionExtension` |
43
+ | `seedgrid-security` | 2026.10.14 | Usuários/papéis, JWT, `PasswordService`, SPIs `JwtTokenCustomizer`, `JwtTokenValidator`, `SecurityScopeResolverProvider`, `SecurityLinkBuilder` |
44
+ | `seedgrid-privacy` | 2026.07.27 | Mascaramento/criptografia de PII do `Tenant` |
45
+ | `seedgrid-audit-core` | 2026.09.25 | `AuditMetadataContributor`, `AuditReadScopeProvider` (`seedgrid-audit-postgresql` só em teste) |
46
+ | `seedgrid-backup` | 2026.10.09 | `backup.spi.TenantProvider` |
47
+ | `seedgrid-billing-core` | 2026.10.14-1 | Conta/trial por tenant, SPIs `CurrentAccountResolver`, `AccountMembersGateway` (`seedgrid-billing-stripe` só em `dependencyManagement`) |
48
+ | `seedgrid-apikeys` | 2026.10.14 | Schema de API keys no master e nos tenants |
49
+ | `seedgrid-notification` (+ `-deployment`) | 2026.10.15 | Catálogo de eventos, regras, outbox e envio por e-mail/WhatsApp; vem transitivamente, o host não precisa declará-la — ver [[SeedGrid/Extensions/notification/Visao-geral\|notification]] |
50
+ | `seedgrid-resilience-core` | 2026.10.13 | `@SeedGridRateLimit` nos endpoints públicos |
51
+
52
+ Quarkus no runtime: `arc`, `hibernate-orm(-panache)`, `jdbc-postgresql`, `agroal`, `flyway`, `rest-jackson`, `hibernate-validator`, `smallrye-openapi`, `opentelemetry`, `micrometer(-registry-prometheus)`, `smallrye-fault-tolerance`, `mailer`, `qute`, `quarkus-mongodb-client` (**optional**), além de `jbcrypt`. O deployment acrescenta `quarkus-scheduler-deployment`, `quarkus-virtual-threads-deployment` e `quarkus-grpc-common-deployment`.
53
+
54
+ Extensions relacionadas: [[SeedGrid/Extensions/security/Visao-geral|security]], [[SeedGrid/Extensions/billing/Visao-geral|billing]], [[SeedGrid/Extensions/audit/Visao-geral|audit]], [[SeedGrid/Extensions/backup/Visao-geral|backup]], [[SeedGrid/Extensions/apikeys/Visao-geral|apikeys]], [[SeedGrid/Extensions/resilience/Visao-geral|resilience]], [[SeedGrid/Extensions/privacy/Visao-geral|privacy]], [[SeedGrid/Extensions/commons/Visao-geral|commons]]. Alternativa com banco compartilhado/login: [[SeedGrid/Extensions/login-tenancy/Visao-geral|login-tenancy]].
55
+
56
+ ## Arquitetura e fluxo
57
+
58
+ ```mermaid
59
+ flowchart LR
60
+ R[Request] --> F1[TenantResolutionFilter<br/>prio AUTHENTICATION-1]
61
+ F1 -->|host -> tenant| C[(TenantContext<br/>RequestScoped)]
62
+ F1 --> F2[TenantMigrationGateFilter<br/>prio AUTHENTICATION]
63
+ F2 -->|login-paths| MC[TenantMigrationCoordinator]
64
+ C --> QR[QuarkusTenantResolver]
65
+ QR --> H[Hibernate PU default<br/>multitenant=DATABASE]
66
+ H --> TCR[TenantConnectionResolverImpl]
67
+ TCR -->|master| MDS[(DataSource master)]
68
+ TCR -->|tenant| POOL[Pool por tenant<br/>cache + LRU + circuit breaker]
69
+ POOL --> TDB[(banco prefix_tenant)]
70
+ ```
71
+
72
+ - **Resolução** (Confirmado): `TenantResolutionFilter` lê `X-Forwarded-Host` (prioritário) ou `Host`, extrai o tenant com `TenantHostExtractor` e grava no `TenantContext` e no MDC (`tenantId`); sem tenant → `master`. O Javadoc do filtro deixa explícito que o default para `master` **não** é fronteira de segurança: o isolamento vem do JWT/API key serem resolvidos no banco do próprio tenant.
73
+ - **JWT**: `SubdomainTenancyJwtTokenCustomizer` adiciona as claims `subdomain` e `audience` (`platform`/`client`); `SubdomainTenancyJwtTokenValidator` rejeita token cujo `subdomain` difere do host.
74
+ - **Conexões**: `TenantConnectionResolverImpl` verifica no master se o tenant existe e está ativo (`TenantNotFoundException` caso contrário), cria `SimpleDriverDataSource` por tenant, aplica limite/evicção e circuit breaker. Detalhes dos parâmetros em [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração]].
75
+ - **Startup** (`MigrationService#onStart`, `@Priority(APPLICATION)`): no master roda, em ordem, security → audit (se `jpa`) → backup → billing → apikeys → ai-pricing/exchange (se habilitados) → subdomain-tenancy → `db/migration/master` (+ additional), depois `AdminUserBootstrapService#seedAdminUserIfNeeded`. Tenants não migram no boot por padrão.
76
+ - **Signup → provisionamento**: `POST /public/tenants` grava o tenant pendente e envia OTP; `POST /public/tenants/validate-email` valida o código e chama `TenantProvisioningService#createTenantWithAdmin` (lock local + `pg_try_advisory_lock`, `CREATE DATABASE` idempotente, migrations security → tenant → audit → backup → apikeys, usuário root; rollback com `DROP DATABASE` se o banco foi criado no fluxo), depois `SubdomainBillingAccounts#ensureFor` (conta + trial no billing) e ativa o tenant.
77
+ - **Migração por tenant**: `TenantMigrationCoordinator` com estado em `tenant_migration_state` (`PENDING`/`MIGRATING`/`READY`/`FAILED`), lock com fencing token e heartbeat. Gatilhos: login (gate devolve `503` + `Retry-After: 15`), endpoint manual, ou boot.
78
+ - **Limpeza**: `TenantCleanupScheduler` remove do master os cadastros não validados mais velhos que o TTL (não remove o banco — ver Limitações).
79
+
80
+ ## APIs públicas e pontos de integração
81
+
82
+ ### Endpoints REST
83
+
84
+ | Método e path | Classe | Acesso | Observação |
85
+ |---|---|---|---|
86
+ | `POST /public/tenants` | `TenantSignupController#signupTenant` | público, rate limit `tenant-signup` (100/min) | `201` CREATED ou `200` UPDATED_PENDING; corpo traz `emailDelivered` |
87
+ | `POST /public/tenants/validate-email` | `#validateEmail` | público, `tenant-validate-email` (10/min) | Body `{subdomain, code}`; dispara o provisionamento |
88
+ | `POST /public/tenants/resend-email?subdomain=` | `#resendEmail` | público, `tenant-resend-email` (5/h) | `400` sem `subdomain` |
89
+ | `GET /public/tenant-openapi` | `TenantOpenApiController#getFilteredOpenApi` | `@PermitAll` + permissão HTTP `permit` no `MPC` | Remove tag `tenant-only` no master; `master-only` + `master-only-paths` nos tenants |
90
+ | `POST /internal/migrations/tenants/{tenantId}/trigger?force=` | `TenantMigrationController#triggerMigration` | só contexto master (`403 master_scope_required`) | Não há `@RolesAllowed` na classe; a autenticação depende da política global do security (**Não confirmado**) |
91
+ | `GET /internal/migrations/tenants/{tenantId}/status` | `#migrationStatus` | idem | — |
92
+ | `GET /internal/health/tenants/{tenantId}` | `TenantHealthController#tenantHealth` | idem | Códigos `OK`, `INVALID_TENANT`, `TENANT_NOT_FOUND`, `TENANT_INACTIVE`, `DATABASE_UNREACHABLE`, `MIGRATION_PENDING/RUNNING/FAILED` |
93
+
94
+ Paths centralizados em `RestControllerPath`. Exceções mapeadas por `SubdomainTenancyExceptionExtension` (`ApiExceptionExtension` do commons): `TenantNotFoundException` → 404, `SubdomainAlreadyExistsException`/`CnpjAlreadyExistsException` → 409, `TenantAlreadyActiveException`/`ValidationCodeExpiredException` → 400.
95
+
96
+ ### Anotações
97
+ - `@MasterOnly` / `@TenantOnly` (`annotations/`): meta-anotações com `@Tag("master-only")`/`@Tag("tenant-only")` — **só documentação/OpenAPI**, não controlam acesso.
98
+
99
+ ### SPIs para o host
100
+
101
+ | Interface | Default fornecido | Uso |
102
+ |---|---|---|
103
+ | `spi.SubdomainTenancyNotificationService#sendValidationNotification(TenantValidationNotification)` | `MailerNotificationService` (`@DefaultBean`, mailer `subdomaintenancy`) | Entrega do OTP |
104
+ | `spi.TemplateSubdomainTenancyProvider#buildValidationEmail(...)` | `DefaultTemplateSubdomainTenancyProvider` (`@DefaultBean`, Qute: procura `templates/subdomain-tenancy/confirmation.html` ou `subdomain-tenancy/html/confirmation.html` do host e cai em `subdomaintenancy-default/html/confirmation.html` da extension) | HTML do OTP quando não há `EmailTemplateProvider` |
105
+ | `spi.EmailTemplateProvider#buildConfirmationEmailHtml(...)`, `#getConfirmationEmailSubject(...)` | nenhum | Opcional; se resolvível, define assunto/HTML |
106
+ | `spi.PasswordEncoder#encode(String)` | `BCryptPasswordEncoder` (`@DefaultBean`) | Injetado como `Instance<PasswordEncoder>` em `TenantService`/`TenantProvisioningService` |
107
+
108
+ Com os defaults `@DefaultBean`, o envio do OTP funciona só com o SMTP configurado (**Confirmado**).
109
+
110
+ ### Beans CDI úteis ao host
111
+ - `TenantContext` (`@RequestScoped`): `getCurrentTenant()`, `setCurrentTenant(String)`, `clear()`.
112
+ - `TenantScopedExecutor#call(Supplier<T>)`: executa em nova transação (`REQUIRES_NEW`).
113
+ - `TenantHostExtractor#extractTenantFromHost(String)`.
114
+ - `TenantAwareMongoProducer` (`@Produces @DefaultBean MongoDatabase`, banco `<prefix>_<tenant>_audit_db`) e `MongoTenantResolverProducer` → `MongoTenantResolver` quando há `MongoClient`.
115
+ - Implementações de SPIs de outras extensions: `MultiTenantProvider` (commons), `MultiTenantBackupProvider` (backup), `TenantAuditScopeProvider` (audit), `SubdomainCurrentAccountResolver`/`SubdomainAccountMembersGateway` (billing), `SubdomainTenancySecurityScopeResolver`, `TenantSecurityLinks` (`@Alternative @Priority(1)`, reescreve links de e-mail do security para `<tenant>.<host>`).
116
+
117
+ ### Métricas (Micrometer)
118
+ `seedgrid.subdomain-tenancy.tenant.datasource.provisioning.failures`, `…connection.failures`, `…connection.circuit.open`, `…active.pools`, `…active.connections.total`, `…connections.active{tenant}`, `…evictions{reason=ttl|lru}`, `seedgrid.subdomain-tenancy.migration.failures`.
119
+
120
+ ## Migrations
121
+
122
+ | Location | Versões | History | Onde roda |
123
+ |---|---|---|---|
124
+ | `db/migration/subdomain-tenancy` | `V100`–`V110` (tenants, root user, campos de trial legados, snapshots, `tenant_migration_state`, fencing token, índices, `tenant_validation_tokens`, hashes de busca de privacy, conta de billing) | `flyway_schema_history_subdomain_tenancy` | master |
125
+ | `db/migration/tenancy` | `V706__timestamps_com_fuso.sql` | — | **Não aplicada** pela extension (nenhuma location aponta para ela). `java/MIGRATION_RANGES.md` documenta a faixa 100–199 e trata a V706 como exceção legada |
126
+ | `db/migration/master` / `db/migration/tenant` (do host) | livres | `flyway_schema_history_master` / `_tenant` | master no boot / tenant no provisionamento e migração |
127
+
128
+ ## Testes
129
+
130
+ `runtime/src/test/java/br/com/seedgrid/subdomaintenancy/` — 17 classes, ~76 métodos `@Test`. **Não executados** nesta análise.
131
+
132
+ - Unitários (Mockito): `TenantHostExtractorTest` (10), `TenantsRepositoryTest` (7), `TenantHealthServiceTest`, `TenantProvisioningServiceTest`, `TenantServiceTest`, `TenantMigrationCoordinatorTest`, `TenantMigrationStateStoreTest`, `TenantResolutionFilterTest`, `TenantHealthControllerTest`, `TenantOpenApiControllerFilterTest`, `TenantAuditScopeProviderTest`, `TenantSecurityLinksTest`, `TenantToStringMaskingTest`.
133
+ - `@QuarkusTest` com Postgres via Testcontainers (`SubdomainTenancyPostgresTestResource`): `TenantRegisterResourceIntegrationTest` (9), `TenantsRepositoryIntegrationTest` (6), `TenantBillingIntegrationTest`, `TenantEncryptedFieldsTest` (perfil com criptografia de privacy).
134
+ - Há também `perf/jmeter/login-concurrency.jmx` (carga de login concorrente).
135
+
136
+ ## Limitações
137
+
138
+ - **Somente PostgreSQL** (`CREATE DATABASE`, `pg_try_advisory_lock`, `pg_terminate_backend`); o usuário de tenant precisa de `CREATE DATABASE`.
139
+ - **Subdomínios com `_`**: o signup aceita `^[a-z0-9_]{5,50}$`, mas o extractor só aceita `^[a-z0-9-]+$` → esses tenants caem em `master` pelo host (**Confirmado**; ver Dúvidas na Configuração).
140
+ - Teto de `max-active-datasources` sem pool ocioso → `TenantProvisioningException` na resolução.
141
+ - `TenantCleanupScheduler` remove só o registro em `tenants`, não o banco físico (contradiz o Javadoc). Se houver banco criado para cadastro não validado, ele fica órfão (o provisionamento só ocorre após validação, então na prática isso só acontece em cenários de falha — **Inferência**).
142
+ - Vários literais `"master"` fixos: `default-tenant` diferente de `master` não é consistente.
@@ -0,0 +1,75 @@
1
+ ---
2
+ tags: [seedgrid, extension, whatsapp, rust, configuracao]
3
+ fonte: rust/seedgrid-rust-crate-whatsapp
4
+ branch: main
5
+ commit: be738a2d1e
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # whatsapp — Configuração Rust
10
+
11
+ Visão do crate: [[SeedGrid/Extensions/whatsapp/Rust|Porte Rust do whatsapp]] · Java: [[SeedGrid/Extensions/whatsapp/Configuracao|Configuração do whatsapp (Java)]].
12
+
13
+ ## Cabeçalho
14
+ | Item | Valor |
15
+ |---|---|
16
+ | Struct | `WhatsAppConfig` — `rust/seedgrid-rust-crate-whatsapp/src/config.rs` |
17
+ | Mecanismo | `WhatsAppConfig::from_env()` (`std::env::var`) ou struct do host |
18
+ | Prefixo Java | `seedgrid.whatsapp` |
19
+ | Quem chama `from_env` | security (`src/state.rs`, para o canal 2FA WhatsApp) |
20
+ | Leitura | quando `from_env()` é chamado (boot); sem recarga |
21
+
22
+ Os nomes de env são exatamente os das expressões `${...}` de `java/seedgrid-quarkus-ext-whatsapp/seedgrid-whatsapp-core/src/main/resources/META-INF/microprofile-config.properties` (**Confirmado**), com os mesmos padrões.
23
+
24
+ ## Variáveis de ambiente
25
+ | Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Leitura | Origem |
26
+ |---|---|---|---|---|---|---|---|---|---|
27
+ | `SEEDGRID_WHATSAPP_API` (Java `seedgrid.whatsapp.api`) | String | `default`, `evolution`, `whatsapp-cloud` (case-insensitive, com `trim`); vazio/desconhecido → `default` | `default` | Não | escolhe o provider em `create_whatsapp_api` | `factory.rs`; `enums.rs` → `WhatsAppProvider::from_config` | define quais variáveis abaixo importam | `from_env()` | `config.rs` → `from_env` |
28
+ | `SEEDGRID_WHATSAPP_REST_CLIENT_EVOLUTION_URL` (Java `seedgrid.whatsapp.rest-client.evolution.url`) | `Option<String>` | URL base da Evolution API | — (vira `""` no transporte) | Condicional: `api=evolution` | base das requisições | `EvolutionWhatsAppApi::new` | `EVOLUTION_API_KEY` | `from_env()` | `evolution.rs` |
29
+ | 🔒 `SEEDGRID_WHATSAPP_EVOLUTION_API_KEY` (Java `seedgrid.whatsapp.evolution.api-key`) | `Option<String>` | texto | — | Condicional: `api=evolution` | ausente/branco → `is_configured() = false` e `IllegalState` na chamada | `EvolutionWhatsAppApi::require_api_key` | — | `from_env()` | `evolution.rs` |
30
+ | `SEEDGRID_WHATSAPP_REST_CLIENT_CLOUD_URL` (Java `seedgrid.whatsapp.rest-client.cloud.url`) | String | URL | `https://graph.facebook.com/v21.0` (`DEFAULT_CLOUD_URL`; vazio → padrão) | Não | base da Graph API | `OfficialCloudWhatsAppApi::new` | — | `from_env()` | `config.rs`, `cloud.rs` |
31
+ | 🔒 `SEEDGRID_WHATSAPP_CLOUD_ACCESS_TOKEN` (Java `seedgrid.whatsapp.cloud.access-token`) | `Option<String>` | token Meta | — | Condicional: `api=whatsapp-cloud` | `Authorization: Bearer`; ausente → `is_configured() = false` e `IllegalState` na chamada | `cloud.rs` | — | `from_env()` | `cloud.rs` |
32
+
33
+ Valores em branco contam como ausentes (`opt_env`), exceto `SEEDGRID_WHATSAPP_API`, que é lido cru (o branco cai em `default` pela fábrica).
34
+
35
+ ## Struct `WhatsAppConfig`
36
+ | Campo | `Default` |
37
+ |---|---|
38
+ | `api` | `"default"` |
39
+ | `evolution_api_key` | `None` |
40
+ | `cloud_access_token` | `None` |
41
+ | `evolution_url` | `None` |
42
+ | `cloud_url` | `DEFAULT_CLOUD_URL` |
43
+
44
+ ## Features Cargo
45
+ Nenhuma.
46
+
47
+ ## Relação com a security
48
+ O canal 2FA por WhatsApp na security usa também `SEEDGRID_SECURITY_2F_WHATSAPP_CHANNEL_ID` (citado em `rust/seedgrid-rust-crate-security/src/state.rs`); essa variável pertence à configuração da security, fora desta nota.
49
+
50
+ ## Somente testes
51
+ Nenhuma variável de ambiente lida em `tests/`.
52
+
53
+ ## Configuração mínima
54
+ ```dotenv
55
+ # Evolution
56
+ SEEDGRID_WHATSAPP_API=evolution
57
+ SEEDGRID_WHATSAPP_REST_CLIENT_EVOLUTION_URL=https://<evolution-host>
58
+ SEEDGRID_WHATSAPP_EVOLUTION_API_KEY=<SEGREDO:evolution-api-key>
59
+ ```
60
+
61
+ ## Configuração completa comentada
62
+ ```dotenv
63
+ SEEDGRID_WHATSAPP_API=whatsapp-cloud # default | evolution | whatsapp-cloud
64
+ # Evolution
65
+ SEEDGRID_WHATSAPP_REST_CLIENT_EVOLUTION_URL=https://<evolution-host>
66
+ SEEDGRID_WHATSAPP_EVOLUTION_API_KEY=<SEGREDO:evolution-api-key>
67
+ # Cloud oficial
68
+ SEEDGRID_WHATSAPP_REST_CLIENT_CLOUD_URL=https://graph.facebook.com/v21.0
69
+ SEEDGRID_WHATSAPP_CLOUD_ACCESS_TOKEN=<SEGREDO:meta-access-token>
70
+ ```
71
+
72
+ ## Receitas
73
+ - **Desligado/dev**: não definir nada → `default` (no-op que loga; `is_configured()` falso).
74
+ - **Evolution**: três variáveis do bloco Evolution.
75
+ - **Cloud oficial**: `SEEDGRID_WHATSAPP_API=whatsapp-cloud` + access token.