@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,229 @@
1
+ ---
2
+ tags: [seedgrid, extension, resilience, exemplos]
3
+ fonte: java/seedgrid-quarkus-ext-resilience
4
+ branch: main
5
+ commit: eff41304f8
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # Exemplos — SeedGrid Resilience (Quarkus)
10
+
11
+ Voltar: [[SeedGrid/Extensions/resilience/Visao-geral|Visão geral do resilience]] · Propriedades: [[SeedGrid/Extensions/resilience/Configuracao|Configuração do resilience]]
12
+
13
+ Pacote base: `br.com.seedgrid.resilience.core.runtime`. Exemplos baseados em `README.md`, `README.adoc` e nas assinaturas reais das anotações/SPIs.
14
+
15
+ ## Dependência
16
+
17
+ ```xml
18
+ <dependency>
19
+ <groupId>br.com.seedgrid</groupId>
20
+ <artifactId>seedgrid-resilience-core</artifactId>
21
+ <version>2026.10.13</version>
22
+ </dependency>
23
+ <!-- estado distribuído: um provider de cache (ex.: Redis) -->
24
+ <dependency>
25
+ <groupId>br.com.seedgrid</groupId>
26
+ <artifactId>seedgrid-cache-redis</artifactId>
27
+ <version>2026.10.13</version>
28
+ </dependency>
29
+ ```
30
+
31
+ Versão `2026.10.13` = pom atual de `seedgrid-quarkus-ext-resilience` e de `seedgrid-quarkus-lib-cache`. **Confirmado**.
32
+
33
+ ## 1. Rate limit básico (por IP)
34
+
35
+ Fonte: `README.md`.
36
+
37
+ ```java
38
+ import java.time.temporal.ChronoUnit;
39
+ import br.com.seedgrid.resilience.core.runtime.ratelimiter.annotations.SeedGridRateLimit;
40
+
41
+ @Path("/auth")
42
+ public class SignupResource {
43
+
44
+ @POST
45
+ @Path("/signup")
46
+ @SeedGridRateLimit(permits = 5, window = 1, windowUnit = ChronoUnit.MINUTES)
47
+ public Response signup(SignupRequest req) { ... } // 5 req/min por IP
48
+ }
49
+ ```
50
+
51
+ Ao estourar: HTTP 429 com `X-RateLimit-Limit`, `X-RateLimit-Remaining: 0`, `X-RateLimit-Reset` e `Retry-After` (via `RateLimitExceptionMapper`).
52
+
53
+ ## 2. Limiters independentes ajustáveis por properties
54
+
55
+ Fonte: `README.md`.
56
+
57
+ ```java
58
+ @SeedGridRateLimit(configKey = "login", permits = 100, window = 1, windowUnit = ChronoUnit.MINUTES)
59
+ public AuthResponseDTO login(LoginDTO dto) { ... }
60
+
61
+ @SeedGridRateLimit(configKey = "export-create", permits = 10, window = 1, windowUnit = ChronoUnit.HOURS)
62
+ public Response createExport(ExportRequest req) { ... }
63
+ ```
64
+
65
+ ```properties
66
+ seedgrid.ratelimit.login.permits=200
67
+ seedgrid.ratelimit.login.window=PT30S
68
+ seedgrid.ratelimit.export-create.algorithm=token-bucket
69
+ seedgrid.ratelimit.export-create.burst-capacity=3
70
+ ```
71
+
72
+ ## 3. Token bucket com rajada e espaçamento mínimo
73
+
74
+ Fonte: `README.adoc` § "Exemplo de Token Bucket".
75
+
76
+ ```java
77
+ import br.com.seedgrid.resilience.core.runtime.ratelimiter.enums.RateLimitAlgorithm;
78
+
79
+ @SeedGridRateLimit(
80
+ permits = 10,
81
+ window = 1,
82
+ windowUnit = ChronoUnit.MINUTES,
83
+ algorithm = RateLimitAlgorithm.TOKEN_BUCKET,
84
+ burstCapacity = 20,
85
+ minSpacing = 200,
86
+ minSpacingUnit = ChronoUnit.MILLIS)
87
+ public void syncProducts() { ... }
88
+ ```
89
+
90
+ `minSpacing` não tem efeito com `FIXED_WINDOW` (ver [[SeedGrid/Extensions/resilience/Visao-geral|Visão geral]]).
91
+
92
+ ## 4. Cota compartilhada entre métodos (`GROUP`)
93
+
94
+ Fonte: `README.adoc` § "Compartilhando cota com GROUP". Observe que `KeyScope` fica no pacote `circuitbreaker.enums`.
95
+
96
+ ```java
97
+ import br.com.seedgrid.resilience.core.runtime.circuitbreaker.enums.KeyScope;
98
+
99
+ @SeedGridRateLimit(permits = 300, window = 1, windowUnit = ChronoUnit.MINUTES,
100
+ group = "email-provider", scope = KeyScope.GROUP)
101
+ public void sendWelcomeEmail(...) { ... }
102
+
103
+ @SeedGridRateLimit(permits = 300, window = 1, windowUnit = ChronoUnit.MINUTES,
104
+ group = "email-provider", scope = KeyScope.GROUP)
105
+ public void sendResetEmail(...) { ... }
106
+ ```
107
+
108
+ ## 5. Contexto sem HTTP (jobs, consumers)
109
+
110
+ Fonte: Javadoc de `SeedGridRateLimit#identity` e `README.adoc`.
111
+
112
+ ```java
113
+ import br.com.seedgrid.resilience.core.runtime.ratelimiter.spi.AnonymousIdentityResolver;
114
+
115
+ @SeedGridRateLimit(permits = 10, window = 1, windowUnit = ChronoUnit.MINUTES,
116
+ identity = AnonymousIdentityResolver.class)
117
+ public void consume(Message msg) { ... }
118
+ ```
119
+
120
+ ## 6. Identidade customizada (SPI `IdentityResolver`)
121
+
122
+ Fonte: `README.md`. Assinatura real: `String resolve(SeedGridInvocationContext ctx)`; nunca retornar `null`.
123
+
124
+ ```java
125
+ import br.com.seedgrid.resilience.core.runtime.core.SeedGridInvocationContext;
126
+ import br.com.seedgrid.resilience.core.runtime.ratelimiter.spi.IdentityResolver;
127
+
128
+ @ApplicationScoped
129
+ public class TenantIdentityResolver implements IdentityResolver {
130
+ @Override
131
+ public String resolve(SeedGridInvocationContext ctx) {
132
+ return ctx.tenantId().orElse("anonymous");
133
+ }
134
+ }
135
+
136
+ @SeedGridRateLimit(permits = 1000, window = 1, windowUnit = ChronoUnit.HOURS,
137
+ identity = TenantIdentityResolver.class)
138
+ public Report generate(...) { ... }
139
+ ```
140
+
141
+ O `RateLimitService` busca o resolver via `CDI.current().select(classe)`; se não for bean resolvível, instancia pelo construtor sem argumentos.
142
+
143
+ ## 7. Publicando tenant/operação no contexto
144
+
145
+ A chave do rate limit inclui os atributos `tenantId` e `operation` do `SeedGridInvocationContext` corrente. A extension não preenche esse contexto; o host pode usar `ThreadLocalSeedGridInvocationContext` (API real: `set`, `clear`, `runWith`, `callWith`). Implementação ilustrativa do contexto (a interface exige `header`, `method`, `path`, `remoteIp`, `attribute`, `tags`):
146
+
147
+ ```java
148
+ import java.util.Map;
149
+ import java.util.Optional;
150
+ import br.com.seedgrid.resilience.core.runtime.core.SeedGridInvocationContext;
151
+ import br.com.seedgrid.resilience.core.runtime.core.ThreadLocalSeedGridInvocationContext;
152
+
153
+ record JobContext(Map<String, Object> attrs) implements SeedGridInvocationContext {
154
+ public Optional<String> header(String name) { return Optional.empty(); }
155
+ public Optional<String> method() { return Optional.empty(); }
156
+ public Optional<String> path() { return Optional.empty(); }
157
+ public Optional<String> remoteIp() { return Optional.empty(); }
158
+ public Optional<Object> attribute(String name) { return Optional.ofNullable(attrs.get(name)); }
159
+ public Optional<Map<String, String>> tags() { return Optional.empty(); }
160
+ }
161
+
162
+ ThreadLocalSeedGridInvocationContext.runWith(
163
+ new JobContext(Map.of("tenantId", "acme", "operation", "sync-products")),
164
+ () -> productSync.syncProducts());
165
+ // chave resultante: rl:acme:sync-products:<escopo>:<identidade>
166
+ ```
167
+
168
+ ## 8. Circuit breaker com fallback
169
+
170
+ Fonte: `README.md`.
171
+
172
+ ```java
173
+ import br.com.seedgrid.resilience.core.runtime.circuitbreaker.annotations.SeedGridCircuitBreaker;
174
+
175
+ @ApplicationScoped
176
+ public class PaymentGateway {
177
+
178
+ @SeedGridCircuitBreaker(
179
+ name = "payments",
180
+ failureRateThreshold = 50.0,
181
+ minimumNumberOfCalls = 20,
182
+ openStateDurationMs = 30_000,
183
+ permittedCallsInHalfOpen = 10,
184
+ fallbackMethod = "paymentsFallback")
185
+ public PaymentResult charge(Order order) { ... }
186
+
187
+ // precisa ser público; procurado como (params..., Exceção), (params...) ou ()
188
+ public PaymentResult paymentsFallback(Order order, Exception cause) { ... }
189
+ }
190
+ ```
191
+
192
+ Sem fallback, circuito aberto lança `CircuitBreakerOpenException` (`circuitName()`, `retryAfterMs()`), que **não** tem `ExceptionMapper` na extension. Exemplo de mapper no host (usa só a API pública da exceção):
193
+
194
+ ```java
195
+ import br.com.seedgrid.resilience.core.runtime.circuitbreaker.exceptions.CircuitBreakerOpenException;
196
+
197
+ @Provider
198
+ public class CircuitOpenMapper implements ExceptionMapper<CircuitBreakerOpenException> {
199
+ @Override
200
+ public Response toResponse(CircuitBreakerOpenException ex) {
201
+ return Response.status(503)
202
+ .header("Retry-After", (ex.retryAfterMs() + 999) / 1000)
203
+ .build();
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## 9. Circuit breaker com slow calls e exceções filtradas
209
+
210
+ ```java
211
+ import br.com.seedgrid.resilience.core.runtime.circuitbreaker.enums.SlidingWindowType;
212
+
213
+ @SeedGridCircuitBreaker(
214
+ name = "erp",
215
+ windowType = SlidingWindowType.TIME_BASED,
216
+ slidingWindowSize = 60, // em TIME_BASED: 60 segundos (e até 60 registros)
217
+ slowCallDurationThresholdMs = 2_000,
218
+ slowCallRateThreshold = 80.0,
219
+ recordExceptions = { java.io.IOException.class },
220
+ ignoreExceptions = { IllegalArgumentException.class })
221
+ public ErpResponse fetch(String id) { ... }
222
+ ```
223
+
224
+ Override em produção:
225
+
226
+ ```properties
227
+ seedgrid.circuitbreaker.erp.slow-call-duration=PT3S
228
+ seedgrid.circuitbreaker.erp.enabled=false # kill switch
229
+ ```
@@ -0,0 +1,80 @@
1
+ ---
2
+ tags: [seedgrid, extension, resilience, rust]
3
+ fonte: rust/seedgrid-rust-crate-resilience
4
+ branch: main
5
+ commit: be738a2d1e
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # resilience — porte Rust (`seedgrid-rust-crate-resilience`)
10
+
11
+ Porte da extensão Quarkus `seedgrid-quarkus-ext-resilience`. Lado Java: [[SeedGrid/Extensions/resilience/Visao-geral|Visão geral do resilience (Java)]] · [[SeedGrid/Extensions/resilience/Configuracao|Configuração do resilience (Java)]]. Configuração Rust: [[SeedGrid/Extensions/resilience/Configuracao-Rust|Configuração Rust do resilience]].
12
+
13
+ Entrega **apenas** Circuit Breaker e Rate Limiter, ambos guardando estado num `SeedGridCacheProvider` (in-memory ou distribuído). Não há retry, timeout nem bulkhead. **Confirmado** (`src/lib.rs`).
14
+
15
+ ## Identificação
16
+ | Item | Valor |
17
+ |---|---|
18
+ | Pacote Cargo | `seedgrid-rust-crate-resilience` (lib `seedgrid_resilience`) |
19
+ | Versão | workspace `2026.10.14` |
20
+ | Features | nenhuma |
21
+ | Dependência SeedGrid | `seedgrid-rust-crate-cache` |
22
+ | Consumidores | security (`src/ratelimit.rs`), backup e billing (seams de rate limit), `seedgrid-runtime` (macros `#[rate_limit]`/`#[circuit_breaker]`) |
23
+
24
+ ## Arquitetura
25
+ No Java, CB e rate limit são interceptors CDI (`@SeedGridCircuitBreaker`, `@SeedGridRateLimit`). Em Rust:
26
+ - **Circuit Breaker**: `circuit_breaker(name, CircuitBreakerConfig, cache)` devolve uma guarda `CircuitBreaker` com `call(fut)` / `call_with_fallback(fut, fallback)`; estado em `CacheBackedCircuitBreakerStore` (chave `cb:<name>`). Config efetiva por `CircuitBreakerConfigResolver::resolve(&SeedGridCircuitBreaker, nome)`.
27
+ - **Rate Limiter**: `RateLimitService::new(cache, Arc<dyn ConfigSource>)` + `try_acquire(&SeedGridRateLimit, &InvocationInfo, ctx)`; algoritmos `FIXED_WINDOW`, `SLIDING_WINDOW`, `TOKEN_BUCKET`; chave `rl:<tenant>[:<operação>]:<escopo>:<identidade>`. Para Axum: `ServiceRateLimiter` + `rate_limit_middleware` (429 + headers `X-RateLimit-*`).
28
+ - Ordem: rate limit é a camada mais externa (prioridade Java 1000), circuit breaker por dentro (1010).
29
+ - As macros de `seedgrid-runtime` (`#[rate_limit]`, `#[circuit_breaker]`) geram essa fiação — ver [[SeedGrid/Extensions/runtime/Visao-geral|Visão geral do runtime]].
30
+
31
+ ## API pública
32
+ | Módulo | Símbolos |
33
+ |---|---|
34
+ | `config` | `ConfigSource` (trait tipada: `get_bool/long/int/double/duration/string`), `EmptyConfigSource`, `MapConfigSource`, `ConfigValue` |
35
+ | `circuitbreaker` | `SeedGridCircuitBreaker`, `CircuitBreakerConfig` (+ builder), `CircuitBreakerConfigResolver`, `CircuitBreakerStore`, `CacheBackedCircuitBreakerStore`, `circuit_breaker`, `CircuitBreaker`, `AsErrorClass`, `ErrorClass`, `CircuitBreakerOpenException`, `SlidingWindowType` |
36
+ | `ratelimiter` | `SeedGridRateLimit`, `InvocationInfo`, `RateLimitService`, `Clock`, `RateLimiter`, `ServiceRateLimiter`, `rate_limit_middleware`, `apply_rate_limit_headers`, `IdentityResolver` (`IpIdentityResolver` — usa `X-Forwarded-For`/`X-Real-IP`; `AnonymousIdentityResolver`), `SeedGridInvocationContext`, `MapInvocationContext`, `RateLimitExceededException`, `RateLimitAlgorithm`, `KeyScope` (`Method`, `Class`, `Group`), `TimeUnit` |
37
+
38
+ ## Exemplos
39
+ Circuit breaker (doc de `src/circuitbreaker/guard.rs`):
40
+ ```rust
41
+ let cache = Arc::new(InMemoryCache::new());
42
+ let cfg = CircuitBreakerConfig::builder().name("backup-upload").build();
43
+ let cb = circuit_breaker("backup-upload", cfg, cache);
44
+ let r: Result<u8, _> = cb.call(async { Ok::<u8, MinhaFalha>(1) }).await;
45
+ ```
46
+ Rate limit numa rota Axum (padrão usado por backup/billing; seams `export_create_rate_limit`, `checkout_rate_limit`…):
47
+ ```rust
48
+ let service = Arc::new(RateLimitService::new(cache, Arc::new(EmptyConfigSource)));
49
+ let limiter: Arc<dyn RateLimiter> = Arc::new(ServiceRateLimiter::new(
50
+ service, seedgrid_billing::routes::checkout_rate_limit(), InvocationInfo::new("POST /billing/checkout", "billing")));
51
+ let route = route.layer(axum::middleware::from_fn_with_state(limiter, rate_limit_middleware));
52
+ ```
53
+ (O segundo bloco é **Inferência** montada a partir das assinaturas de `ServiceRateLimiter::new` e do doc de `rate_limit_middleware`; não há esse trecho literal no repositório.) Exemplo executável com macros: `rust/seedgrid-runtime/examples/resilience_audit.rs`.
54
+
55
+ ## Testes
56
+ `tests/circuit_breaker_config_resolver.rs`, `circuit_breaker_store.rs`, `http_headers.rs`, `identity_resolver.rs`, `rate_limit_algorithms.rs`, `rate_limit_service_config_override.rs` (+ unitários). **Não executados.** Sem app em `rust/examples/`.
57
+
58
+ ## Notas de comportamento
59
+
60
+ Valores inválidos de `algorithm`/`window-type` e config inválida (`permits <= 0`, `window <= 0`) causam **panic**.
61
+
62
+ ## Enums
63
+ Java: [[SeedGrid/Extensions/resilience/Enums|Enums do resilience (Java)]].
64
+
65
+ | Enum | Arquivo | Java equivalente |
66
+ |---|---|---|
67
+ | `RateLimitAlgorithm` | `src/ratelimiter/enums.rs` | [[SeedGrid/Extensions/resilience/Enums#RateLimitAlgorithm\|RateLimitAlgorithm]] |
68
+ | `KeyScope` | idem | [[SeedGrid/Extensions/resilience/Enums#KeyScope\|KeyScope]] |
69
+ | `TimeUnit` | idem | `java.time.temporal.ChronoUnit` (subconjunto) |
70
+ | `CircuitState` | `src/circuitbreaker/enums.rs` | [[SeedGrid/Extensions/resilience/Enums#CircuitState\|CircuitState]] |
71
+ | `SlidingWindowType` | idem | [[SeedGrid/Extensions/resilience/Enums#SlidingWindowType\|SlidingWindowType]] |
72
+ | `CircuitBreakerError<E>` (`Open`, `Inner`) | `src/circuitbreaker/guard.rs` | exceções do circuito |
73
+ | `ConfigValue` (`Bool`, `Long`, `Int`, `Double`, `Duration`, `Str`) | `src/config.rs` | tipo do valor numa `MapConfigSource` |
74
+
75
+ Nenhum tem serde. Os nomes em texto vêm da `ConfigSource` (normalizados por trim, maiúsculas, `-` → `_`).
76
+ - **RateLimitAlgorithm**: `FixedWindow` (`FIXED_WINDOW`, padrão), `TokenBucket` (`TOKEN_BUCKET`), `SlidingWindow` (`SLIDING_WINDOW`). Valor desconhecido na config → *panic*. Iguais ao Java.
77
+ - **KeyScope**: `Method` (padrão), `Class`, `Group` (cai na assinatura do método se `group` vazio). Só definido em código (não há override por config).
78
+ - **TimeUnit**: `Millis`, `Seconds`, `Minutes`, `Hours`. Nota: o Java aceita qualquer `ChronoUnit`; o Rust só estes 4.
79
+ - **CircuitState**: `Closed` (0), `Open` (1), `HalfOpen` (2). Os ordinais entram na serialização binária do estado no cache (`ordinal`/`from_ordinal`; ordinal inválido → `Closed`). Iguais ao Java.
80
+ - **SlidingWindowType**: `CountBased` (padrão), `TimeBased` (o tamanho da janela vira segundos). A config `window-type` aceita `COUNT_BASED`/`TIME_BASED` (inválido → *panic*).
@@ -0,0 +1,112 @@
1
+ ---
2
+ tags: [seedgrid, extension, resilience]
3
+ fonte: java/seedgrid-quarkus-ext-resilience
4
+ branch: main
5
+ commit: eff41304f8
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # SeedGrid Resilience (Quarkus) — Visão geral
10
+
11
+ Extension Quarkus de **rate limiting** e **circuit breaker** declarativos. O desenvolvedor anota métodos/classes CDI com `@SeedGridRateLimit` ou `@SeedGridCircuitBreaker`; interceptors CDI aplicam a regra e guardam o estado no `SeedGridCacheProvider` (lib `seedgrid-cache`), de modo que, com um provider distribuído (Redis/Valkey/Dragonfly), o limite/circuito é compartilhado entre instâncias. A anotação define os valores padrão; com `configKey` (rate limit) ou `name` (circuit breaker) os valores podem ser sobrescritos por properties em runtime.
12
+
13
+ - Configuração completa: [[SeedGrid/Extensions/resilience/Configuracao|Configuração do resilience]]
14
+ - Enums (`RateLimitAlgorithm`, `KeyScope`, `SlidingWindowType`, `CircuitState`): [[SeedGrid/Extensions/resilience/Enums|Enums do resilience]]
15
+ - Exemplos: [[SeedGrid/Extensions/resilience/Exemplos|Exemplos do resilience]]
16
+ - Crate equivalente: [[SeedGrid/Extensions/resilience/Rust|Porte Rust]]
17
+ - Dependência de cache: [[SeedGrid/Extensions/cache/Visao-geral|Visão geral do cache]]
18
+
19
+ > Não é baseada em MicroProfile Fault Tolerance / SmallRye Fault Tolerance: não há dependência `quarkus-smallrye-fault-tolerance` nem uso de `@Retry`/`@CircuitBreaker` do MP. Propriedades no formato `<classe>/<método>/<Anotação>/<param>` **não** se aplicam. **Confirmado** (poms e imports).
20
+
21
+ ## Módulos e coordenadas
22
+
23
+ | Módulo | groupId | artifactId | Versão | Papel |
24
+ |---|---|---|---|---|
25
+ | raiz (`pom.xml`) | `br.com.seedgrid` | `seedgrid-resilience-core-parent` | `2026.10.13` | POM agregador; parent `seedgrid-parent:2026.07.02`. Módulos: `deployment`, `runtime` |
26
+ | `runtime` | `br.com.seedgrid` | `seedgrid-resilience-core` | `2026.10.13` | Anotações, interceptors, serviços, SPI, filtros JAX-RS. Gera `META-INF/jandex.idx` (jandex-maven-plugin) |
27
+ | `deployment` | `br.com.seedgrid` | `seedgrid-resilience-core-deployment` | `2026.10.13` | `SeedgridResilienceCoreProcessor`: só registra a feature `seedgrid-resilience-core` |
28
+ | `integration-tests` | `br.com.seedgrid` | `seedgrid-resilience-core-integration-tests` | — | **Não** listado em `<modules>` do POM raiz; contém apenas um recurso "hello" de template |
29
+
30
+ Observações:
31
+ - `quarkus-extension.yaml`: nome "SeedGrid Resilience Core", status `preview`.
32
+
33
+ ## Dependências
34
+
35
+ | Dependência | Escopo | Uso |
36
+ |---|---|---|
37
+ | `io.quarkus:quarkus-arc` | compile | CDI / interceptors |
38
+ | `br.com.seedgrid:seedgrid-cache-core:2026.10.13` | compile | `SeedGridCacheProvider` (`increment`, `getBytes`, `putBytes`, `withLock`) |
39
+ | `jakarta.ws.rs:jakarta.ws.rs-api` | provided | `RateLimitExceptionMapper`, `RateLimitContainerResponseFilter` |
40
+ | `io.quarkus:quarkus-vertx-http` | provided | `IpIdentityResolver` (injeção opcional de `HttpServerRequest`) |
41
+ | `seedgrid-cache-redis`, `quarkus-redis-client`, testcontainers, mockito | test | Testes |
42
+
43
+ Provider de cache: o `seedgrid-cache-core` traz `DefaultSeedGridCache` como `@DefaultBean` (em memória); os providers `seedgrid-cache-redis`/`-valkey`/`-dragonfly` são `@Alternative`. Sem um provider distribuído no classpath, o estado de rate limit/circuit breaker fica **por processo** (`DefaultSeedGridCache`, `@DefaultBean` em `seedgrid-cache-core`; o teste `SeedgridResilienceCoreTest` sobe sem provider nem config). **Confirmado**.
44
+
45
+ ## Arquitetura e fluxo
46
+
47
+ ```mermaid
48
+ flowchart LR
49
+ CALL["Chamada a método anotado"] --> RLI["RateLimitInterceptor<br/>prioridade LIBRARY_BEFORE"]
50
+ RLI --> RLS["RateLimitService<br/>(lê seedgrid.ratelimit.{configKey}.*)"]
51
+ RLS --> IDR["IdentityResolver"]
52
+ RLS --> ALG["Fixed / Sliding / Token bucket"]
53
+ ALG --> CACHE[("SeedGridCacheProvider")]
54
+ RLI -->|"negado"| EX429["RateLimitExceededException → 429"]
55
+ RLI --> CBI["CircuitBreakerInterceptor<br/>prioridade LIBRARY_BEFORE+10"]
56
+ CBI --> CBR["CircuitBreakerConfigResolver<br/>(lê seedgrid.circuitbreaker.{name}.*)"]
57
+ CBI --> STORE["CacheBackedCircuitBreakerStore"] --> CACHE
58
+ CBI --> M["método de negócio / fallbackMethod"]
59
+ ```
60
+
61
+ ### Rate limit (`@SeedGridRateLimit`)
62
+ 1. `RateLimitInterceptor` resolve a anotação: método → método na classe alvo → hierarquia de classes → interfaces.
63
+ 2. `RateLimitService#tryAcquire`: se `configKey` não vazio e `seedgrid.ratelimit.{configKey}.enabled=false`, permite sem contar. Senão resolve identidade, permits, janela, min-spacing, burst e algoritmo (config vence anotação quando há `configKey`), valida (`permits > 0`, `window > 0`, `burstCapacity >= 0`, `minSpacing >= 0`, senão `IllegalArgumentException`).
64
+ 3. Chave no cache: `rl:{tenant}:{operation}:{scope}:{identity}` (sem `operation` quando vazio). `tenant` vem do atributo `tenantId` do `SeedGridInvocationContext` (padrão `no-tenant`); `operation` do atributo `operation`. `scope`: `METHOD` → assinatura do método; `CLASS` → nome da classe alvo; `GROUP` → `group` (ou assinatura se vazio).
65
+ 4. Algoritmos:
66
+ - `FIXED_WINDOW`: `cache.increment(key, window)`. **Não aplica `minSpacing`** (o campo é ignorado nesse algoritmo — **Confirmado**).
67
+ - `SLIDING_WINDOW`: lista de timestamps sob `withLock`; aplica `minSpacing`.
68
+ - `TOKEN_BUCKET`: capacidade = `burstCapacity` (ou `permits` se 0), reposição `permits/window`; aplica `minSpacing`.
69
+ 5. Negado → `RateLimitExceededException`; `RateLimitExceptionMapper` (JAX-RS) responde **429** com `X-RateLimit-Limit`, `X-RateLimit-Remaining: 0`, `X-RateLimit-Reset` e `Retry-After` (segundos, arredondados para cima).
70
+ 6. Permitido e `emitHeaders=true` → `RateLimitHeaderCarrier` (ThreadLocal) e `RateLimitContainerResponseFilter` acrescentam `X-RateLimit-Limit/Remaining/Reset` à resposta.
71
+
72
+ ### Circuit breaker (`@SeedGridCircuitBreaker`)
73
+ 1. `CircuitBreakerInterceptor` resolve a anotação (método → método na classe alvo → hierarquia).
74
+ 2. Kill switch: `seedgrid.circuitbreaker.{name}.enabled=false` → chama o método direto.
75
+ 3. Nome do circuito = `name` ou, se vazio, assinatura `Classe#metodo(tipos)`. Chave no cache: `cb:{nome}`.
76
+ 4. `CacheBackedCircuitBreakerStore` (sob `withLock` de 1 s): estados `CLOSED` → `OPEN` (taxa de falha ≥ `failureRateThreshold` ou, se `slowCallDurationThresholdMs > 0`, taxa de lentas ≥ `slowCallRateThreshold`, após `minimumNumberOfCalls`) → `HALF_OPEN` após `openStateDurationMs` → `CLOSED` quando `permittedCallsInHalfOpen` sondas têm sucesso, ou volta a `OPEN` na primeira falha.
77
+ 5. Janela: ring buffer de tamanho `slidingWindowSize`. Em `TIME_BASED`, `slidingWindowSize` é **também** a duração em segundos da janela (e o tamanho máximo do buffer). Alterar `slidingWindowSize` descarta o estado salvo. TTL do estado: `max(openStateDurationMs*2, 1h)`.
78
+ 6. Exceções: conta como falha se for subclasse de algum `recordExceptions` e não de `ignoreExceptions` (ignore vence).
79
+ 7. Circuito aberto → `CircuitBreakerOpenException(circuitName, retryAfterMs)`; com `fallbackMethod`, chama o fallback. O fallback também é chamado em **qualquer** exceção do método (mesmo as ignoradas). Assinaturas procuradas, em ordem: `(params..., TipoDaExceção ou superclasse)`, `(params...)`, `()`; precisa ser método **público** (`Class#getMethod`).
80
+
81
+ ## APIs públicas e pontos de integração
82
+
83
+ | Tipo | Símbolo | Descrição |
84
+ |---|---|---|
85
+ | Anotação (InterceptorBinding) | `...ratelimiter.annotations.SeedGridRateLimit` | `permits`, `window`, `windowUnit`, `algorithm`, `burstCapacity`, `minSpacing`, `minSpacingUnit`, `identity`, `group`, `scope`, `emitHeaders`, `configKey` (detalhes em [[SeedGrid/Extensions/resilience/Configuracao\|Configuração]]) |
86
+ | Anotação (InterceptorBinding) | `...circuitbreaker.annotations.SeedGridCircuitBreaker` | `name`, `slidingWindowSize`, `windowType`, `failureRateThreshold`, `slowCallDurationThresholdMs`, `slowCallRateThreshold`, `minimumNumberOfCalls`, `openStateDurationMs`, `permittedCallsInHalfOpen`, `recordExceptions`, `ignoreExceptions`, `fallbackMethod` |
87
+ | SPI | `...ratelimiter.spi.IdentityResolver` | `String resolve(SeedGridInvocationContext ctx)`; prontos: `IpIdentityResolver` (X-Forwarded-For → X-Real-IP → remote address → `anonymous`), `AnonymousIdentityResolver` |
88
+ | SPI | `...circuitbreaker.spi.CircuitBreakerStore` | `allowCall`, `onSuccess`, `onError`; implementação `CacheBackedCircuitBreakerStore` (`@ApplicationScoped`, sem `@DefaultBean`) |
89
+ | SPI | `...ratelimiter.algorithm.RateLimitAlgorithmStrategy` | `acquire(SeedGridCacheProvider, RateLimitRequest)` |
90
+ | Contexto | `...core.SeedGridInvocationContext` / `ThreadLocalSeedGridInvocationContext` | `set/clear/runWith/callWith`; atributos `tenantId` e `operation` entram na chave do rate limit. A extension **não** popula esse contexto: o host precisa fazê-lo (senão usa `EmptySeedGridInvocationContext`) |
91
+ | Beans CDI | `RateLimitService`, `RateLimitService.Clock`, `CircuitBreakerConfigResolver`, algoritmos | `@ApplicationScoped` |
92
+ | Exceções | `RateLimitExceededException` (mapeada para 429), `CircuitBreakerOpenException` | `CircuitBreakerOpenException` **não** tem `ExceptionMapper` na extension — **Inferência**: em endpoint REST sem fallback vira erro 500 se o host não mapear |
93
+ | JAX-RS | `RateLimitExceptionMapper`, `RateLimitContainerResponseFilter` (`@Provider`) | Headers `X-RateLimit-*`, `Retry-After` |
94
+
95
+ Endpoints REST próprios: nenhum no runtime (o `integration-tests` tem só `GET /seedgrid-resilience-core` de template). Migrations: nenhuma. Eventos: nenhum.
96
+
97
+ ## Testes existentes (não executados)
98
+
99
+ | Local | Tipo | Cobre |
100
+ |---|---|---|
101
+ | `runtime/src/test/.../ratelimiter/RateLimitAlgorithmsIT.java` | IT com Redis (Testcontainers, `disabledWithoutDocker`) | Os 3 algoritmos: limite, reset, min-spacing, burst, refill |
102
+ | `runtime/src/test/.../circuitbreaker/CircuitBreakerStoreIT.java` | IT com Redis | Transições de estado, slow calls, record/ignore, janela por tempo, persistência |
103
+ | `runtime/src/test/.../ratelimiter/services/RateLimitServiceConfigOverrideTest.java` | Unitário (Mockito `Config`) | Overrides `seedgrid.ratelimit.checkout.*`, kebab-case em `algorithm`, sem `configKey` ignora config |
104
+ | `runtime/src/test/.../circuitbreaker/configs/CircuitBreakerConfigResolverTest.java` | Unitário | Overrides `seedgrid.circuitbreaker.payments.*`, sem `name` ignora config, `enabled` |
105
+ | `runtime/src/test/.../ratelimiter/spi/IpIdentityResolverTest.java`, `AnonymousIdentityResolverTest.java` | Unitário | Resolução de identidade |
106
+ | `deployment/src/test/.../SeedgridResilienceCoreTest.java`, `SeedgridResilienceCoreDevModeTest.java` | QuarkusUnitTest / DevModeTest | Boot sem camada HTTP e sem config |
107
+
108
+ ## Limitações
109
+ - `minSpacing` não tem efeito com `FIXED_WINDOW`.
110
+ - O kill switch do rate limiter (`enabled=false`) ainda emite headers `X-RateLimit-*` (limite = `permits` da anotação), quando `emitHeaders=true`.
111
+ - Valores inválidos de `algorithm`/`window-type` só falham na chamada (`IllegalArgumentException` de `Enum.valueOf`), não no boot.
112
+ - `CircuitBreakerOpenException` sem mapper HTTP.
@@ -0,0 +1,90 @@
1
+ ---
2
+ tags: [seedgrid, extension, runtime, rust, configuracao]
3
+ fonte: rust/seedgrid-runtime, rust/seedgrid-macros
4
+ branch: main
5
+ commit: be738a2d1e
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # runtime — Configuração Rust
10
+
11
+ Visão geral: [[SeedGrid/Extensions/runtime/Visao-geral|Visão geral do runtime]]. Não há equivalente Java (o análogo é o comportamento do container Quarkus).
12
+
13
+ ## Cabeçalho
14
+ | Item | Valor |
15
+ |---|---|
16
+ | Mecanismos | (1) `BootstrapOptions` em runtime; (2) **atributos das macros** (fixados em compilação); (3) uma leitura de ambiente: `default_gate(key)` |
17
+ | Variáveis de ambiente | **nenhum nome fixo**: `default_gate` lê a variável cujo nome é o valor de `#[scheduled(enabled_if = "...")]` (`rust/seedgrid-runtime/src/bootstrap.rs` → `default_gate`) |
18
+ | Features Cargo | nenhuma (nas duas crates) |
19
+
20
+ ## Runtime: `BootstrapOptions` e gate
21
+ | Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Leitura | Origem |
22
+ |---|---|---|---|---|---|---|---|---|---|
23
+ | `BootstrapOptions.fail_fast` | bool | — | `true` | Não | primeiro erro de `#[startup]` aborta o boot (`Err` com contexto); `false` loga e segue | `run_startups` | — | `bootstrap_with` | `src/bootstrap.rs` |
24
+ | `BootstrapOptions.run_startup` | bool | — | `true` | Não | roda as `#[startup]` | `bootstrap_with` | — | idem | idem |
25
+ | `BootstrapOptions.run_scheduled` | bool | — | `true` | Não | spawna as `#[scheduled]` | `bootstrap_with` | — | idem | idem |
26
+ | `BootstrapOptions.gate` (`with_gate`) | `Fn(&str) -> bool` | — | `default_gate` | Não | decide se cada `enabled_if` está ligado | `spawn_all_scheduled` | `enabled_if` | idem | idem |
27
+ | `<valor de enabled_if>` (env var dinâmica) | bool | `true`, `1`, `yes`, `on` (case-insensitive, `trim`); ausente/outro = desligado | desligado | Condicional: só para tasks com `enabled_if` | task não é spawnada quando falso (log `info`) | `default_gate` | substituível por `with_gate` | no `bootstrap` | `src/bootstrap.rs` → `default_gate` |
28
+
29
+ No workspace, nenhuma crate de plataforma declara `enabled_if`; só aparecem `FEATURE_REPORTS` (teste `tests/macros.rs`) e `REPORTS_ENABLED` (doc) como exemplos.
30
+
31
+ ## Macros (configuração em compilação)
32
+
33
+ ### `#[startup]`
34
+ | Atributo | Tipo | Padrão | Observação |
35
+ |---|---|---|---|
36
+ | `priority` | i32 | `100` (neutra; `seedgrid-macros/src/lib.rs` → `startup`) | menor roda antes |
37
+
38
+ ### `#[scheduled]` (exatamente um de `cron`/`every`)
39
+ | Atributo | Tipo | Padrão | Observação |
40
+ |---|---|---|---|
41
+ | `cron` | String | — | quartz de 6 campos (`seg min hora dia-mes mes dia-semana`); inválido → task não iniciada (log de erro) |
42
+ | `every` | String | — | `Nms`, `Ns`, `Nm`, `Nh`, `Nd`; zero/inválido → não iniciada |
43
+ | `enabled_if` | String | — (sempre ligada) | nome da env var do gate |
44
+ | `concurrent` | bool | `false` (SKIP) | `true` = PROCEED |
45
+ | `delay` | String | — | atraso inicial, mesmo formato de `every` |
46
+
47
+ ### `#[default_provider]`
48
+ `spi` (obrigatório), `provider`, `priority` (padrão `0`; maior vence em `resolve_spi`).
49
+
50
+ ### `#[rate_limit]` (→ `seedgrid-resilience`)
51
+ | Atributo | Padrão | Observação |
52
+ |---|---|---|
53
+ | `config_key` | `""` | habilita overrides `seedgrid.ratelimit.<key>.*` da `ConfigSource` do `RateLimitService` ([[SeedGrid/Extensions/resilience/Configuracao-Rust\|Configuração Rust do resilience]]) |
54
+ | `permits` | `5` | |
55
+ | `window` | `"30s"` | |
56
+ | `algorithm` | `"fixed_window"` | `fixed_window`, `sliding_window`, `token_bucket` |
57
+ | `service` | — | **obrigatório**: caminho de função que devolve `Arc<RateLimitService>` |
58
+ | `context` | `EmptySeedGridInvocationContext` | expressão de contexto (limite global por método se omitido) |
59
+
60
+ Retorno exigido: `Result<T, E>` com `E: From<RateLimitExceededException>`.
61
+
62
+ ### `#[circuit_breaker]` (→ `seedgrid-resilience`)
63
+ | Atributo | Padrão | Observação |
64
+ |---|---|---|
65
+ | `name` | — | **obrigatório** |
66
+ | `failure_rate_threshold` | `50.0` | |
67
+ | `open_state_duration` | `"30s"` | |
68
+ | `minimum_number_of_calls` | `20` | |
69
+ | `sliding_window_size` | `100` | |
70
+ | `cache` | — | **obrigatório**: provider de `Arc<InMemoryCache>` ou `Arc<dyn SeedGridCacheProvider>` |
71
+ | `fallback` | — | função sem argumentos com o mesmo `Result<T, E>` |
72
+
73
+ ### `#[audited]` (→ `seedgrid-audit`)
74
+ `entity` (FQCN, obrigatório), `operation` (`CREATE`, `UPDATE`, `DELETE`, `READ`, `OTHER`; obrigatório), `description`, `auditor` (obrigatório; provider de `impl Auditor`), `user` (expr), `context` (expr `AuditContext`, vence `user`), `before` (expr avaliada antes do corpo), `mask_rules` (expr `Vec<MaskRule>`), `capture_return` (padrão `true` em CREATE/UPDATE; serializa `Ok(T)`, exige `T: Serialize`). Audita só no sucesso.
75
+
76
+ ### `#[derive(PersonalData)]` (→ `seedgrid-privacy`)
77
+ Atributo de campo `#[personal_data(...)]` (alias `#[encrypted(...)]`): `search = "campo_hash"`, `normalize = "email" | "canonical" | "none"` (padrão `canonical`), `nested` (desce em struct que também deriva `PersonalData`). Ver [[SeedGrid/Extensions/privacy/Configuracao-Rust|Configuração Rust do privacy]].
78
+
79
+ Fonte de todos os atributos: `rust/seedgrid-macros/src/lib.rs` (parsers `meta.path.is_ident(...)`) e README do runtime. Padrões de `#[startup]` e `#[rate_limit]` conferidos nos parsers; os de `#[circuit_breaker]` são `Option` no parser e o valor efetivo vem dos defaults de `SeedGridCircuitBreaker` (README; **Inferência** coerente com `config_resolver.rs`); os de `#[audited]` vêm do README.
80
+
81
+ ## Somente exemplos/testes
82
+ | Variável | Onde |
83
+ |---|---|
84
+ | `FEATURE_REPORTS` | `tests/macros.rs` (gate de teste) |
85
+ | `RUST_LOG` | não usado; os exemplos fixam o nível em código |
86
+
87
+ ## Receitas
88
+ - **Ligar uma rotina só em produção**: `#[scheduled(cron = "0 0 3 * * ?", enabled_if = "NIGHTLY_JOB_ENABLED")]` + `NIGHTLY_JOB_ENABLED=true` no ambiente.
89
+ - **Gate a partir de outra fonte (ex.: arquivo de config)**: `bootstrap_with(BootstrapOptions::new().with_gate(|k| minha_config.flag(k)))`.
90
+ - **Boot tolerante a falhas**: `BootstrapOptions { fail_fast: false, ..Default::default() }`.
@@ -0,0 +1,114 @@
1
+ ---
2
+ tags: [seedgrid, extension, runtime, rust]
3
+ fonte: rust/seedgrid-runtime, rust/seedgrid-macros
4
+ branch: main
5
+ commit: be738a2d1e
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # runtime — Visão geral (só Rust: `seedgrid-runtime` + `seedgrid-macros`)
10
+
11
+ Par de crates que reproduz em Rust, **em tempo de compilação**, o auto-wiring do Quarkus/CDI que os hosts SeedGrid têm no Java: anotar funções e chamar um único `bootstrap()`. Não há extensão Java equivalente (é infraestrutura do lado Rust). Configuração e referência dos atributos das macros: [[SeedGrid/Extensions/runtime/Configuracao-Rust|Configuração Rust do runtime]].
12
+
13
+ | Crate | Papel |
14
+ |---|---|
15
+ | `seedgrid-runtime` (lib `seedgrid_runtime`) | registro `inventory`, `bootstrap()`/`RuntimeHandles`, motor de agendamento (cron/intervalo), eventos ao usuário (`events`); reexporta as macros, `inventory` e `anyhow` |
16
+ | `seedgrid-macros` (lib `seedgrid_macros`, `proc-macro = true`) | attribute macros `#[startup]`, `#[scheduled]`, `#[default_provider]`, `#[rate_limit]`, `#[circuit_breaker]`, `#[audited]` e o derive `#[derive(PersonalData)]` |
17
+
18
+ Ambas: versão do workspace `2026.10.14`, edição 2021, MSRV 1.82, sem features Cargo. `seedgrid-runtime` depende de `seedgrid-macros`, `inventory 0.3`, `tokio`, `chrono`; resilience, cache e audit são só **dev-dependencies** (exemplo/teste). Recomendação do README: depender só de `seedgrid-runtime` (o código gerado usa `::seedgrid_runtime::...`); a exceção é `#[derive(PersonalData)]`, que gera `::seedgrid_privacy::...` e é reexportado pela crate privacy.
19
+
20
+ ## Mapeamento CDI/Quarkus → Rust
21
+ | Java | Rust | Fase |
22
+ |---|---|---|
23
+ | `@Observes StartupEvent` / `@Startup` + `@Priority` | `#[startup(priority = N)]` (menor roda antes) | 1 |
24
+ | `@Scheduled(cron / every)` | `#[scheduled(cron = "...")]` / `#[scheduled(every = "...")]` | 1 |
25
+ | gate `{config.key}` do `@Scheduled` | `#[scheduled(..., enabled_if = "ENV_VAR")]` | 1 |
26
+ | `ConcurrentExecution.SKIP` / `PROCEED` | `concurrent = false` (padrão) / `true` | 1 |
27
+ | `@DefaultBean` | `#[default_provider(spi, provider, priority)]` + `resolve_spi` (só metadados; maior prioridade vence) | 1 |
28
+ | `@SeedGridRateLimit` | `#[rate_limit(...)]` → `seedgrid-resilience` | 2 |
29
+ | `@SeedGridCircuitBreaker` | `#[circuit_breaker(...)]` → `seedgrid-resilience` | 2 |
30
+ | `@Audit` | `#[audited(...)]` → `seedgrid-audit` | 3 |
31
+ | `@PersonalData` / `@EncryptedField` | `#[derive(PersonalData)]` → `seedgrid-privacy` | — |
32
+ | `Event<SgUserEvent>` + `@Observes(AFTER_SUCCESS)` | `events::UserEventSink` / `SgUserEvent` | — |
33
+ | boot / shutdown do container | `bootstrap()`, `bootstrap_with(opts)` / `RuntimeHandles::shutdown()` | 1 |
34
+
35
+ Fonte: `rust/seedgrid-runtime/src/lib.rs`.
36
+
37
+ ## Arquitetura
38
+ ```mermaid
39
+ flowchart LR
40
+ A["#[startup] / #[scheduled] / #[default_provider]"] -- inventory::submit! --> R[(slices globais em link-time)]
41
+ R --> B["bootstrap()"]
42
+ B -->|ordem de priority| S["roda #[startup] (sequencial)"]
43
+ B -->|enabled_if via gate| T["spawn_scheduled_boxed (tokio)"]
44
+ T --> H[RuntimeHandles]
45
+ L["libs: spawn_scheduled(SpawnConfig, factory)"] --> T
46
+ ```
47
+ - Fase 1 usa `inventory`: o linker precisa manter os símbolos (funciona em binários, exemplos e testes de integração; numa lib nunca referenciada pelo binário, itens podem ser descartados — caveat do README).
48
+ - Fases 2/3 **não** usam `inventory`: são interceptors por função que embrulham o corpo e obtêm dependências via funções-provider sem argumentos (`service`, `cache`, `auditor`).
49
+ - `startup` com erro: `fail_fast = true` (padrão) aborta o boot; senão loga e continua. Erros de `#[scheduled]` só são logados (a task sobrevive).
50
+
51
+ ## API pública (`seedgrid-runtime`)
52
+ | Grupo | Símbolos | Fonte |
53
+ |---|---|---|
54
+ | Boot | `bootstrap()`, `bootstrap_with(BootstrapOptions)`, `BootstrapOptions { fail_fast, run_startup, run_scheduled, gate }`, `BootstrapOptions::with_gate`, `default_gate`, `EnabledGate` | `src/bootstrap.rs` |
55
+ | Handles | `RuntimeHandles::{scheduled_task_count, task_names, abort_all, shutdown}` | idem |
56
+ | Agendamento programático (para libs) | `spawn_scheduled(SpawnConfig, factory) -> JoinHandle`, `SpawnConfig::{new, concurrent, with_initial_delay}`, `RunSchedule::{cron, every, from_spec}`, `JobFactory` | `src/scheduler.rs` |
57
+ | Cron / duração | `CronSchedule::{parse, matches, next_after}` (quartz de 6 campos: `*`, `?`, `*/n`, `a-b`, `a,b`; dia da semana 0=domingo, aceita 7), `parse_duration` (`ms`, `s`, `m`, `h`, `d`; zero → `None`) | `src/cron.rs` |
58
+ | Registro | `StartupItem`, `ScheduledItem`, `ScheduleSpec`, `SpiBinding`, `TaskRunner`, `BoxFuture`, `resolve_spi`, `startup_items_sorted`, `scheduled_items` | `src/registry.rs` |
59
+ | Eventos | `events::{SgUserEvent, UserEventSink, NoopUserEventSink, noop_user_event_sink}` | `src/events.rs` |
60
+
61
+ ## Quem usa no workspace (**Confirmado** por grep)
62
+ | Crate | O que usa |
63
+ |---|---|
64
+ | backup | `events::{UserEventSink, SgUserEvent, noop_user_event_sink}` (aviso de export concluído) |
65
+ | realtime | `events::*` (implementa `UserEventSink`), `parse_duration` |
66
+ | subdomain-tenancy | `spawn_scheduled`, `RunSchedule`, `SpawnConfig`, `CronSchedule`, `events` |
67
+ | privacy | `#[derive(PersonalData)]` (via `seedgrid-macros`) |
68
+ | — | Nenhuma crate de plataforma usa `#[startup]`, `#[scheduled]`, `#[rate_limit]`, `#[circuit_breaker]` ou `#[audited]` fora dos exemplos/testes do runtime |
69
+
70
+ ## Exemplo (de `rust/seedgrid-runtime/examples/quickstart.rs`)
71
+ ```rust
72
+ use anyhow::Result;
73
+ use seedgrid_runtime::{scheduled, startup};
74
+
75
+ #[startup(priority = 10)]
76
+ async fn seed_database() -> Result<()> { Ok(()) }
77
+
78
+ #[scheduled(every = "2s")]
79
+ async fn heartbeat() -> Result<()> { Ok(()) }
80
+
81
+ #[scheduled(cron = "0 0 * * * *")]
82
+ async fn hourly_report() -> Result<()> { Ok(()) }
83
+
84
+ #[tokio::main]
85
+ async fn main() -> Result<()> {
86
+ let handles = seedgrid_runtime::bootstrap().await?;
87
+ tokio::time::sleep(std::time::Duration::from_millis(5100)).await;
88
+ handles.shutdown().await;
89
+ Ok(())
90
+ }
91
+ ```
92
+ Fases 2/3 completas: `rust/seedgrid-runtime/examples/resilience_audit.rs` (`cargo run -p seedgrid-runtime --example resilience_audit`). API programática (doc de `src/scheduler.rs`):
93
+ ```rust
94
+ let schedule = RunSchedule::cron("0 0 * * * ?").expect("cron valido");
95
+ let handle = spawn_scheduled(SpawnConfig::new("nightly_cleanup", schedule), || async { Ok(()) });
96
+ ```
97
+
98
+ ## Testes
99
+ `rust/seedgrid-runtime/tests/macros.rs` (coleta e ordem de `#[startup]`, gate `enabled_if` ligado/desligado, task disparando, `default_provider`) e `tests/resilience_audit.rs` (macros de Fase 2/3); unitários em `src/cron.rs`. **Não executados.**
100
+
101
+ ## Limitações
102
+ - `#[startup]`/`#[scheduled]` não recebem argumentos; a função deve ser `async fn() -> anyhow::Result<()>` ou `-> ()`.
103
+ - Empate de `priority` tem ordem indefinida.
104
+ - `#[default_provider]` registra só metadados (sem DI real) — roadmap do README.
105
+
106
+ ## Enums
107
+ Sem equivalente Java. Públicos:
108
+
109
+ | Enum | Arquivo | Variantes / uso |
110
+ |---|---|---|
111
+ | `ScheduleSpec` | `seedgrid-runtime/src/registry.rs` | `Cron(&'static str)` ou `Every(&'static str)` — gerado por `#[scheduled(cron=…)]` / `#[scheduled(every=…)]` (exatamente um dos dois) |
112
+ | `RunSchedule` | `seedgrid-runtime/src/scheduler.rs` | `Interval(Duration)` ou `Cron(CronSchedule)` — schedule já compilado (`RunSchedule::cron`, `every`, `from_spec`); `None` se a expressão for inválida |
113
+
114
+ As macros (`seedgrid-macros`) não definem enums; o derive `PersonalData` usa `SearchNormalization` ([[SeedGrid/Extensions/privacy/Rust#Enums|Enums do privacy Rust]]).