@luanpdd/kit-mcp 1.34.0 → 1.36.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 (118) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2 -2
  3. package/bin/mcp.js +6 -6
  4. package/bin/ui.js +74 -74
  5. package/gates/ai-prompt-stability.md +120 -120
  6. package/gates/budget-description.md +68 -68
  7. package/gates/confidence.md +29 -29
  8. package/gates/dependency-check.md +33 -33
  9. package/gates/dept-cycle-prevention.md +179 -179
  10. package/gates/golden-signals-coverage.md +133 -133
  11. package/gates/legacy-refactor-safety.md +178 -178
  12. package/gates/multi-tenant-rls-coverage.md +102 -102
  13. package/gates/no-personal-uuid.md +72 -72
  14. package/gates/obs-agents-mcp-supabase.md +86 -86
  15. package/gates/obs-skills-frontmatter.md +76 -76
  16. package/gates/observability-coverage.md +151 -151
  17. package/gates/omm-no-regression.md +83 -83
  18. package/gates/postmortem-template-required.md +127 -127
  19. package/gates/prr-checklist-coverage.md +128 -128
  20. package/gates/regression.md +32 -32
  21. package/gates/release-pipeline-policy.md +132 -132
  22. package/gates/secrets-scan.md +33 -33
  23. package/gates/service-role-not-in-user-facing.md +113 -113
  24. package/gates/skill-must-include.md +71 -71
  25. package/gates/sync-idempotent.md +62 -62
  26. package/gates/verify-phase-goal.md +34 -34
  27. package/kit/agents/designer-ui.md +216 -216
  28. package/kit/agents/workflow-generator.md +537 -0
  29. package/kit/commands/adicionar-backlog.md +1 -1
  30. package/kit/commands/adicionar-fase.md +1 -1
  31. package/kit/commands/adicionar-tarefa.md +1 -1
  32. package/kit/commands/auditar-observabilidade.md +103 -103
  33. package/kit/commands/auditar-toil.md +129 -129
  34. package/kit/commands/caracterizar-prompt.md +195 -195
  35. package/kit/commands/criar-workflow.md +158 -0
  36. package/kit/commands/definir-perfil.md +1 -1
  37. package/kit/commands/definir-slo.md +108 -108
  38. package/kit/commands/fio.md +1 -1
  39. package/kit/commands/golden-signals.md +142 -142
  40. package/kit/commands/instrumentar-fase.md +200 -200
  41. package/kit/commands/investigar-producao.md +162 -162
  42. package/kit/commands/observabilidade.md +118 -118
  43. package/kit/commands/postmortem.md +179 -179
  44. package/kit/commands/prr.md +205 -205
  45. package/kit/commands/publicar-rapido.md +207 -207
  46. package/kit/commands/risk-budget.md +220 -220
  47. package/kit/commands/sre.md +230 -230
  48. package/kit/file-manifest.json +5 -2
  49. package/kit/framework/references/output-style.md +22 -22
  50. package/kit/hooks/post-apply-migration.js +199 -199
  51. package/kit/hooks/sidecar-tool-publisher.js +210 -210
  52. package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
  53. package/kit/skills/_shared-legacy/glossary.md +389 -389
  54. package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
  55. package/kit/skills/_shared-observability/glossary.md +396 -396
  56. package/kit/skills/_shared-sre/glossary.md +712 -712
  57. package/kit/skills/_shared-supabase/glossary.md +234 -234
  58. package/kit/skills/blameless-postmortems/SKILL.md +340 -340
  59. package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
  60. package/kit/skills/cascading-failures/SKILL.md +311 -311
  61. package/kit/skills/core-analysis-loop/SKILL.md +352 -352
  62. package/kit/skills/distributed-tracing/SKILL.md +362 -362
  63. package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -0
  64. package/kit/skills/eliminating-toil/SKILL.md +243 -243
  65. package/kit/skills/event-based-slos/SKILL.md +296 -296
  66. package/kit/skills/four-golden-signals/SKILL.md +314 -314
  67. package/kit/skills/hermetic-builds/SKILL.md +323 -323
  68. package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
  69. package/kit/skills/llm-as-dependency/SKILL.md +436 -436
  70. package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
  71. package/kit/skills/observability-driven-development/SKILL.md +315 -315
  72. package/kit/skills/observability-maturity-model/SKILL.md +222 -222
  73. package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
  74. package/kit/skills/production-readiness-review/SKILL.md +305 -305
  75. package/kit/skills/release-engineering/SKILL.md +367 -367
  76. package/kit/skills/retry-strategies/SKILL.md +372 -372
  77. package/kit/skills/sre-risk-management/SKILL.md +221 -221
  78. package/kit/skills/structured-events/SKILL.md +265 -265
  79. package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
  80. package/kit/skills/supabase-database-functions/SKILL.md +332 -332
  81. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
  82. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
  83. package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
  84. package/kit/skills/supabase-storage/SKILL.md +234 -234
  85. package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
  86. package/kit/skills/telemetry-sampling/SKILL.md +256 -256
  87. package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
  88. package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
  89. package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
  90. package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
  91. package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
  92. package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
  93. package/kit/skills/ui-tipografia/SKILL.md +211 -211
  94. package/package.json +1 -1
  95. package/src/cli/index.js +1114 -1114
  96. package/src/cli/render.js +194 -194
  97. package/src/cli/upgrade-check.js +135 -135
  98. package/src/core/error-redaction.js +76 -76
  99. package/src/core/failures.js +153 -153
  100. package/src/core/gate-runner.js +205 -205
  101. package/src/core/gates.js +82 -82
  102. package/src/core/logger.js +170 -170
  103. package/src/core/manifest-verify.js +174 -174
  104. package/src/core/metrics.js +268 -268
  105. package/src/core/notify.js +60 -60
  106. package/src/core/path-safety.js +141 -141
  107. package/src/core/replays.js +120 -120
  108. package/src/core/ui.js +185 -185
  109. package/src/mcp-server/install.js +149 -149
  110. package/src/mcp-server/roots.js +124 -124
  111. package/src/ui/auto-spawn.js +113 -113
  112. package/src/ui/browser.js +78 -78
  113. package/src/ui/client.js +130 -130
  114. package/src/ui/events.js +65 -65
  115. package/src/ui/lockfile.js +191 -191
  116. package/src/ui/port.js +67 -67
  117. package/src/ui/server.js +547 -547
  118. package/src/ui/wrapper.js +129 -129
@@ -1,265 +1,265 @@
1
- ---
2
- name: structured-events
3
- description: Use ao instrumentar — wide events de alta cardinalidade (1/request), campos canônicos com dot notation, evite logs unstructured e métricas pre-aggregated.
4
- ---
5
-
6
- # Observabilidade — Structured Events (Wide Events)
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill quando instrumentar código para emitir telemetria. Trigger phrases:
11
-
12
- - "structured logging", "wide events", "observability events"
13
- - "instrumentar handler", "emitir telemetria", "log estruturado"
14
- - "como salvar evento de request"
15
- - "campos canônicos", "atributos de span"
16
- - "alta cardinalidade", "debug por user_id"
17
-
18
- ## Regras absolutas
19
-
20
- - **1 evento por request** — não múltiplos. Acumule contexto durante o request, emita 1 wide event no final (ou em erros).
21
- - **Wide é melhor que narrow** — adicione campos liberalmente. Custo de 100 campos/evento ≈ 10 campos. Disco é barato; falta de campo no incidente é caro.
22
- - **Alta cardinalidade é OBRIGATÓRIA** — `user.id`, `tenant_id`, `request.id`, `customer.email`. Sem isso, observabilidade não funciona (Cap 1).
23
- - **Dot notation OTel** — `user.id` (não `userId` nem `user_id`). `error.type`, `http.status_code`, `db.query`. Snake_case apenas em colunas de DB.
24
- - **NUNCA pre-aggregate** — não emita "p99 latency = 247ms"; emita o `duration_ms` cru de cada request. Aggregation no read time.
25
- - **Estruturado, não texto livre** — JSON, OTel attributes, ou colunas tipadas. **Nunca** `console.log("user 123 did X at 12:34")`.
26
- - **Errors são especiais** — sample 100% de eventos com `result.success = false`. Sucesso pode ser samplado (skill `telemetry-sampling`).
27
- - **Capture context, não code** — emita atributos de business logic (`customer.tier`, `feature_flag.x`), não estado interno de código (`var_x_value_at_line_42`).
28
-
29
- ## Patterns canônicos
30
-
31
- ### Pattern: handler instrumentado (Node/TypeScript)
32
-
33
- ```ts
34
- // PT-BR: 1 evento por request, alta cardinalidade, atributos canônicos
35
- import { trace, SpanStatusCode } from '@opentelemetry/api'
36
-
37
- const tracer = trace.getTracer('orders-service')
38
-
39
- export async function handlePlaceOrder(req: Request) {
40
- return tracer.startActiveSpan('place_order', async (span) => {
41
- // PT-BR: campos canônicos sempre — alta cardinalidade
42
- span.setAttribute('user.id', req.user.id)
43
- span.setAttribute('tenant_id', req.user.tenant)
44
- span.setAttribute('customer.tier', req.user.tier)
45
- span.setAttribute('request.id', req.headers['x-request-id'])
46
- span.setAttribute('endpoint', '/api/v1/orders')
47
- span.setAttribute('http.method', 'POST')
48
- span.setAttribute('build_id', process.env.BUILD_ID ?? 'dev')
49
-
50
- // PT-BR: feature flags como dimensões
51
- span.setAttribute('feature_flag.new_pricing', req.flags.newPricing)
52
-
53
- try {
54
- const order = await createOrder(req.body)
55
-
56
- // PT-BR: result e atributos de domínio
57
- span.setAttribute('result.success', true)
58
- span.setAttribute('order.id', order.id)
59
- span.setAttribute('order.amount_cents', order.amount)
60
- span.setAttribute('order.items_count', order.items.length)
61
- span.setAttribute('http.status_code', 200)
62
- span.setStatus({ code: SpanStatusCode.OK })
63
- return order
64
- } catch (e) {
65
- // PT-BR: erros — sample 100%, classificar por tipo
66
- span.setAttribute('result.success', false)
67
- span.setAttribute('error.type', classifyError(e))
68
- span.setAttribute('error.message', e.message)
69
- span.setAttribute('http.status_code', e.statusCode ?? 500)
70
- span.setStatus({ code: SpanStatusCode.ERROR, message: e.message })
71
- throw e
72
- } finally {
73
- span.end() // PT-BR: SEMPRE — duration_ms é calculado aqui
74
- }
75
- })
76
- }
77
-
78
- function classifyError(e: any): string {
79
- if (e.code === 'P2002') return 'db_conflict'
80
- if (e.statusCode === 401) return 'auth'
81
- if (e.statusCode === 403) return 'authz'
82
- if (e.statusCode === 422) return 'validation'
83
- if (e.statusCode === 429) return 'rate_limit'
84
- if (e.code === 'ETIMEDOUT') return 'timeout'
85
- return 'unknown'
86
- }
87
- ```
88
-
89
- ### Pattern: Edge Function (Deno) com structured event
90
-
91
- ```ts
92
- // PT-BR: Supabase Edge Function — 1 evento estruturado por invocação
93
- import { trace } from 'npm:@opentelemetry/api@1.9.0'
94
-
95
- const tracer = trace.getTracer('edge-process-emails')
96
-
97
- Deno.serve(async (req) => {
98
- return tracer.startActiveSpan('process_emails', async (span) => {
99
- const requestId = crypto.randomUUID()
100
- span.setAttribute('request.id', requestId)
101
- span.setAttribute('build_id', Deno.env.get('SUPABASE_GIT_SHA') ?? 'local')
102
-
103
- try {
104
- const body = await req.json()
105
- span.setAttribute('user.id', body.user_id)
106
- span.setAttribute('tenant_id', body.tenant_id)
107
- span.setAttribute('email.batch_size', body.emails?.length ?? 0)
108
-
109
- const result = await processBatch(body.emails)
110
-
111
- span.setAttribute('result.success', true)
112
- span.setAttribute('email.sent_count', result.sent)
113
- span.setAttribute('email.failed_count', result.failed)
114
- span.setAttribute('duration_ms', result.duration)
115
-
116
- return new Response(JSON.stringify(result), { status: 200 })
117
- } catch (e) {
118
- span.setAttribute('result.success', false)
119
- span.setAttribute('error.type', classify(e))
120
- span.setAttribute('error.message', String(e))
121
- return new Response(JSON.stringify({ error: 'failed' }), { status: 500 })
122
- } finally {
123
- span.end()
124
- }
125
- })
126
- })
127
- ```
128
-
129
- ### Pattern: campos canônicos por categoria
130
-
131
- | Categoria | Campos | Exemplo |
132
- |---|---|---|
133
- | **Identidade** | `user.id`, `tenant_id`, `session.id` | `"550e8400-e29b-41d4-..."` |
134
- | **Request** | `request.id`, `endpoint`, `http.method`, `http.status_code` | `"req_abc123"`, `"/api/v1/orders"`, `"POST"`, `200` |
135
- | **Resultado** | `result.success`, `error.type`, `error.message` | `true`, `"validation"`, `"email already exists"` |
136
- | **Performance** | `duration_ms`, `db.query_count`, `cache.hit` | `127`, `3`, `true` |
137
- | **Build/Deploy** | `build_id`, `service.version`, `region` | `"abc123f"`, `"v1.9.0"`, `"us-east-1"` |
138
- | **Business** | `customer.tier`, `order.amount_cents`, `feature_flag.<name>` | `"pro"`, `4990`, `true` |
139
- | **Tracing** | `trace.id`, `span.id`, `span.parent_id` | (auto via OTel) |
140
-
141
- ### Pattern: query observability — encontrar pattern em wide events
142
-
143
- ```sql
144
- -- PT-BR: alta cardinalidade permite group by ad hoc — sem schema rigido
145
- -- Exemplo: qual tenant + endpoint + error_type domina os erros da última hora?
146
- select
147
- tenant_id,
148
- endpoint,
149
- error_type,
150
- count(*) as error_count,
151
- avg(duration_ms) as avg_duration
152
- from observability.events
153
- where
154
- result_success = false
155
- and timestamp > now() - interval '1 hour'
156
- group by tenant_id, endpoint, error_type
157
- order by error_count desc
158
- limit 20;
159
- ```
160
-
161
- ## Anti-patterns
162
-
163
- ### ANTI: log unstructured
164
-
165
- ```ts
166
- // PT-BR: BAD — não estruturado, não queryable, sem alta cardinalidade
167
- console.log(`User ${userId} placed order ${orderId} for $${amount}`)
168
-
169
- // PT-BR: GOOD — structured wide event
170
- span.setAttribute('user.id', userId)
171
- span.setAttribute('order.id', orderId)
172
- span.setAttribute('order.amount_cents', amount * 100)
173
- ```
174
-
175
- ### ANTI: pre-aggregate em métricas
176
-
177
- ```ts
178
- // PT-BR: BAD — pre-aggregation perde alta cardinalidade
179
- metrics.histogram('order_latency_ms').record(duration, { service: 'orders' })
180
-
181
- // PT-BR: GOOD — emit raw event, agregue no read
182
- span.setAttribute('duration_ms', duration)
183
- // PT-BR: ao queryar: SELECT percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)
184
- // FROM events GROUP BY tenant_id, endpoint -- alta cardinalidade preservada!
185
- ```
186
-
187
- ### ANTI: múltiplos eventos por request
188
-
189
- ```ts
190
- // PT-BR: BAD — 5 eventos para 1 request, sem trace context
191
- log('user_action_started', { user_id })
192
- log('user_action_db_query', { user_id, query })
193
- log('user_action_email_sent', { user_id, to })
194
- log('user_action_completed', { user_id })
195
- log('user_action_response_sent', { user_id, status })
196
-
197
- // PT-BR: GOOD — 1 wide event acumulando contexto
198
- const span = tracer.startSpan('user_action')
199
- span.setAttribute('user.id', user_id)
200
- // ... ao longo do handler, span.setAttribute('email.recipient', ...) etc.
201
- span.end() // 1 evento emitido com todos os atributos
202
- ```
203
-
204
- ### ANTI: cardinalidade baixa
205
-
206
- ```ts
207
- // PT-BR: BAD — apenas service e endpoint, sem identidade
208
- span.setAttribute('service', 'orders')
209
- span.setAttribute('endpoint', '/place')
210
- // PT-BR: durante incident você não consegue responder "afeta quem?"
211
-
212
- // PT-BR: GOOD — adicione identidades de alta cardinalidade
213
- span.setAttribute('user.id', '550e8400-...')
214
- span.setAttribute('tenant_id', 'acme-corp')
215
- span.setAttribute('customer.tier', 'pro')
216
- // PT-BR: durante incident: "afeta quem?" → group by customer.tier, tenant_id
217
- ```
218
-
219
- ### ANTI: capturar valores internos de código
220
-
221
- ```ts
222
- // PT-BR: BAD — atributos sobre estado de variáveis, não sobre business
223
- span.setAttribute('var_temp_array_length', tempArr.length)
224
- span.setAttribute('loop_iteration', i)
225
-
226
- // PT-BR: GOOD — atributos sobre business + identidade
227
- span.setAttribute('order.items_count', items.length)
228
- span.setAttribute('user.id', userId)
229
- ```
230
-
231
- ### ANTI: nomes inconsistentes de atributos
232
-
233
- ```ts
234
- // PT-BR: BAD — mesmo conceito com nomes diferentes em handlers diferentes
235
- span.setAttribute('userId', user.id) // handler A
236
- span.setAttribute('user_id', user.id) // handler B
237
- span.setAttribute('user', user.id) // handler C
238
- // PT-BR: query `WHERE user_id = X` falha em handler A; agg cross-handler quebra
239
-
240
- // PT-BR: GOOD — convenção única em todo o projeto
241
- span.setAttribute('user.id', user.id) // sempre dot notation OTel
242
- ```
243
-
244
- ## Verificação
245
-
246
- Antes de marcar instrumentação completa:
247
-
248
- 1. **1 evento por request** — em request de exemplo, contar eventos emitidos. Deve ser 1 (ou 2 se houver retry interno).
249
- 2. **Atributos canônicos presentes** — checar `user.id`, `tenant_id`, `request.id`, `result.success`, `endpoint`, `duration_ms` no evento emitido.
250
- 3. **Alta cardinalidade verificada** — `select count(distinct user_id)` deve crescer com tráfego real (não estagnar em N pequeno).
251
- 4. **`result.success` define SLI** — boolean confiável para alimentar SLO downstream (ver skill `event-based-slos`).
252
- 5. **Erros têm `error.type` enum** — não `error.message` cru. Permite group by por categoria.
253
- 6. **Build_id presente** — permite comparar versão antes vs depois de deploy.
254
- 7. **Smoke local** — emitir 100 eventos sintéticos, queryar via `select * from events where user_id = X` deve retornar todos.
255
-
256
- ---
257
-
258
- ## Ver também
259
-
260
- - `kit/skills/_shared-observability/glossary.md` — termos canônicos, campos canônicos, anti-patterns
261
- - `kit/skills/distributed-tracing/SKILL.md` — como spans se conectam em traces
262
- - `kit/skills/opentelemetry-standard/SKILL.md` — SDK e exporters
263
- - `kit/skills/core-analysis-loop/SKILL.md` — como queryar wide events para debug
264
-
265
- *Material-fonte: Observability Engineering (O'Reilly, 2022) — Cap 5: "Structured Events Are the Building Blocks of Observability".*
1
+ ---
2
+ name: structured-events
3
+ description: Use ao instrumentar — wide events de alta cardinalidade (1/request), campos canônicos com dot notation, evite logs unstructured e métricas pre-aggregated.
4
+ ---
5
+
6
+ # Observabilidade — Structured Events (Wide Events)
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando instrumentar código para emitir telemetria. Trigger phrases:
11
+
12
+ - "structured logging", "wide events", "observability events"
13
+ - "instrumentar handler", "emitir telemetria", "log estruturado"
14
+ - "como salvar evento de request"
15
+ - "campos canônicos", "atributos de span"
16
+ - "alta cardinalidade", "debug por user_id"
17
+
18
+ ## Regras absolutas
19
+
20
+ - **1 evento por request** — não múltiplos. Acumule contexto durante o request, emita 1 wide event no final (ou em erros).
21
+ - **Wide é melhor que narrow** — adicione campos liberalmente. Custo de 100 campos/evento ≈ 10 campos. Disco é barato; falta de campo no incidente é caro.
22
+ - **Alta cardinalidade é OBRIGATÓRIA** — `user.id`, `tenant_id`, `request.id`, `customer.email`. Sem isso, observabilidade não funciona (Cap 1).
23
+ - **Dot notation OTel** — `user.id` (não `userId` nem `user_id`). `error.type`, `http.status_code`, `db.query`. Snake_case apenas em colunas de DB.
24
+ - **NUNCA pre-aggregate** — não emita "p99 latency = 247ms"; emita o `duration_ms` cru de cada request. Aggregation no read time.
25
+ - **Estruturado, não texto livre** — JSON, OTel attributes, ou colunas tipadas. **Nunca** `console.log("user 123 did X at 12:34")`.
26
+ - **Errors são especiais** — sample 100% de eventos com `result.success = false`. Sucesso pode ser samplado (skill `telemetry-sampling`).
27
+ - **Capture context, não code** — emita atributos de business logic (`customer.tier`, `feature_flag.x`), não estado interno de código (`var_x_value_at_line_42`).
28
+
29
+ ## Patterns canônicos
30
+
31
+ ### Pattern: handler instrumentado (Node/TypeScript)
32
+
33
+ ```ts
34
+ // PT-BR: 1 evento por request, alta cardinalidade, atributos canônicos
35
+ import { trace, SpanStatusCode } from '@opentelemetry/api'
36
+
37
+ const tracer = trace.getTracer('orders-service')
38
+
39
+ export async function handlePlaceOrder(req: Request) {
40
+ return tracer.startActiveSpan('place_order', async (span) => {
41
+ // PT-BR: campos canônicos sempre — alta cardinalidade
42
+ span.setAttribute('user.id', req.user.id)
43
+ span.setAttribute('tenant_id', req.user.tenant)
44
+ span.setAttribute('customer.tier', req.user.tier)
45
+ span.setAttribute('request.id', req.headers['x-request-id'])
46
+ span.setAttribute('endpoint', '/api/v1/orders')
47
+ span.setAttribute('http.method', 'POST')
48
+ span.setAttribute('build_id', process.env.BUILD_ID ?? 'dev')
49
+
50
+ // PT-BR: feature flags como dimensões
51
+ span.setAttribute('feature_flag.new_pricing', req.flags.newPricing)
52
+
53
+ try {
54
+ const order = await createOrder(req.body)
55
+
56
+ // PT-BR: result e atributos de domínio
57
+ span.setAttribute('result.success', true)
58
+ span.setAttribute('order.id', order.id)
59
+ span.setAttribute('order.amount_cents', order.amount)
60
+ span.setAttribute('order.items_count', order.items.length)
61
+ span.setAttribute('http.status_code', 200)
62
+ span.setStatus({ code: SpanStatusCode.OK })
63
+ return order
64
+ } catch (e) {
65
+ // PT-BR: erros — sample 100%, classificar por tipo
66
+ span.setAttribute('result.success', false)
67
+ span.setAttribute('error.type', classifyError(e))
68
+ span.setAttribute('error.message', e.message)
69
+ span.setAttribute('http.status_code', e.statusCode ?? 500)
70
+ span.setStatus({ code: SpanStatusCode.ERROR, message: e.message })
71
+ throw e
72
+ } finally {
73
+ span.end() // PT-BR: SEMPRE — duration_ms é calculado aqui
74
+ }
75
+ })
76
+ }
77
+
78
+ function classifyError(e: any): string {
79
+ if (e.code === 'P2002') return 'db_conflict'
80
+ if (e.statusCode === 401) return 'auth'
81
+ if (e.statusCode === 403) return 'authz'
82
+ if (e.statusCode === 422) return 'validation'
83
+ if (e.statusCode === 429) return 'rate_limit'
84
+ if (e.code === 'ETIMEDOUT') return 'timeout'
85
+ return 'unknown'
86
+ }
87
+ ```
88
+
89
+ ### Pattern: Edge Function (Deno) com structured event
90
+
91
+ ```ts
92
+ // PT-BR: Supabase Edge Function — 1 evento estruturado por invocação
93
+ import { trace } from 'npm:@opentelemetry/api@1.9.0'
94
+
95
+ const tracer = trace.getTracer('edge-process-emails')
96
+
97
+ Deno.serve(async (req) => {
98
+ return tracer.startActiveSpan('process_emails', async (span) => {
99
+ const requestId = crypto.randomUUID()
100
+ span.setAttribute('request.id', requestId)
101
+ span.setAttribute('build_id', Deno.env.get('SUPABASE_GIT_SHA') ?? 'local')
102
+
103
+ try {
104
+ const body = await req.json()
105
+ span.setAttribute('user.id', body.user_id)
106
+ span.setAttribute('tenant_id', body.tenant_id)
107
+ span.setAttribute('email.batch_size', body.emails?.length ?? 0)
108
+
109
+ const result = await processBatch(body.emails)
110
+
111
+ span.setAttribute('result.success', true)
112
+ span.setAttribute('email.sent_count', result.sent)
113
+ span.setAttribute('email.failed_count', result.failed)
114
+ span.setAttribute('duration_ms', result.duration)
115
+
116
+ return new Response(JSON.stringify(result), { status: 200 })
117
+ } catch (e) {
118
+ span.setAttribute('result.success', false)
119
+ span.setAttribute('error.type', classify(e))
120
+ span.setAttribute('error.message', String(e))
121
+ return new Response(JSON.stringify({ error: 'failed' }), { status: 500 })
122
+ } finally {
123
+ span.end()
124
+ }
125
+ })
126
+ })
127
+ ```
128
+
129
+ ### Pattern: campos canônicos por categoria
130
+
131
+ | Categoria | Campos | Exemplo |
132
+ |---|---|---|
133
+ | **Identidade** | `user.id`, `tenant_id`, `session.id` | `"550e8400-e29b-41d4-..."` |
134
+ | **Request** | `request.id`, `endpoint`, `http.method`, `http.status_code` | `"req_abc123"`, `"/api/v1/orders"`, `"POST"`, `200` |
135
+ | **Resultado** | `result.success`, `error.type`, `error.message` | `true`, `"validation"`, `"email already exists"` |
136
+ | **Performance** | `duration_ms`, `db.query_count`, `cache.hit` | `127`, `3`, `true` |
137
+ | **Build/Deploy** | `build_id`, `service.version`, `region` | `"abc123f"`, `"v1.9.0"`, `"us-east-1"` |
138
+ | **Business** | `customer.tier`, `order.amount_cents`, `feature_flag.<name>` | `"pro"`, `4990`, `true` |
139
+ | **Tracing** | `trace.id`, `span.id`, `span.parent_id` | (auto via OTel) |
140
+
141
+ ### Pattern: query observability — encontrar pattern em wide events
142
+
143
+ ```sql
144
+ -- PT-BR: alta cardinalidade permite group by ad hoc — sem schema rigido
145
+ -- Exemplo: qual tenant + endpoint + error_type domina os erros da última hora?
146
+ select
147
+ tenant_id,
148
+ endpoint,
149
+ error_type,
150
+ count(*) as error_count,
151
+ avg(duration_ms) as avg_duration
152
+ from observability.events
153
+ where
154
+ result_success = false
155
+ and timestamp > now() - interval '1 hour'
156
+ group by tenant_id, endpoint, error_type
157
+ order by error_count desc
158
+ limit 20;
159
+ ```
160
+
161
+ ## Anti-patterns
162
+
163
+ ### ANTI: log unstructured
164
+
165
+ ```ts
166
+ // PT-BR: BAD — não estruturado, não queryable, sem alta cardinalidade
167
+ console.log(`User ${userId} placed order ${orderId} for $${amount}`)
168
+
169
+ // PT-BR: GOOD — structured wide event
170
+ span.setAttribute('user.id', userId)
171
+ span.setAttribute('order.id', orderId)
172
+ span.setAttribute('order.amount_cents', amount * 100)
173
+ ```
174
+
175
+ ### ANTI: pre-aggregate em métricas
176
+
177
+ ```ts
178
+ // PT-BR: BAD — pre-aggregation perde alta cardinalidade
179
+ metrics.histogram('order_latency_ms').record(duration, { service: 'orders' })
180
+
181
+ // PT-BR: GOOD — emit raw event, agregue no read
182
+ span.setAttribute('duration_ms', duration)
183
+ // PT-BR: ao queryar: SELECT percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)
184
+ // FROM events GROUP BY tenant_id, endpoint -- alta cardinalidade preservada!
185
+ ```
186
+
187
+ ### ANTI: múltiplos eventos por request
188
+
189
+ ```ts
190
+ // PT-BR: BAD — 5 eventos para 1 request, sem trace context
191
+ log('user_action_started', { user_id })
192
+ log('user_action_db_query', { user_id, query })
193
+ log('user_action_email_sent', { user_id, to })
194
+ log('user_action_completed', { user_id })
195
+ log('user_action_response_sent', { user_id, status })
196
+
197
+ // PT-BR: GOOD — 1 wide event acumulando contexto
198
+ const span = tracer.startSpan('user_action')
199
+ span.setAttribute('user.id', user_id)
200
+ // ... ao longo do handler, span.setAttribute('email.recipient', ...) etc.
201
+ span.end() // 1 evento emitido com todos os atributos
202
+ ```
203
+
204
+ ### ANTI: cardinalidade baixa
205
+
206
+ ```ts
207
+ // PT-BR: BAD — apenas service e endpoint, sem identidade
208
+ span.setAttribute('service', 'orders')
209
+ span.setAttribute('endpoint', '/place')
210
+ // PT-BR: durante incident você não consegue responder "afeta quem?"
211
+
212
+ // PT-BR: GOOD — adicione identidades de alta cardinalidade
213
+ span.setAttribute('user.id', '550e8400-...')
214
+ span.setAttribute('tenant_id', 'acme-corp')
215
+ span.setAttribute('customer.tier', 'pro')
216
+ // PT-BR: durante incident: "afeta quem?" → group by customer.tier, tenant_id
217
+ ```
218
+
219
+ ### ANTI: capturar valores internos de código
220
+
221
+ ```ts
222
+ // PT-BR: BAD — atributos sobre estado de variáveis, não sobre business
223
+ span.setAttribute('var_temp_array_length', tempArr.length)
224
+ span.setAttribute('loop_iteration', i)
225
+
226
+ // PT-BR: GOOD — atributos sobre business + identidade
227
+ span.setAttribute('order.items_count', items.length)
228
+ span.setAttribute('user.id', userId)
229
+ ```
230
+
231
+ ### ANTI: nomes inconsistentes de atributos
232
+
233
+ ```ts
234
+ // PT-BR: BAD — mesmo conceito com nomes diferentes em handlers diferentes
235
+ span.setAttribute('userId', user.id) // handler A
236
+ span.setAttribute('user_id', user.id) // handler B
237
+ span.setAttribute('user', user.id) // handler C
238
+ // PT-BR: query `WHERE user_id = X` falha em handler A; agg cross-handler quebra
239
+
240
+ // PT-BR: GOOD — convenção única em todo o projeto
241
+ span.setAttribute('user.id', user.id) // sempre dot notation OTel
242
+ ```
243
+
244
+ ## Verificação
245
+
246
+ Antes de marcar instrumentação completa:
247
+
248
+ 1. **1 evento por request** — em request de exemplo, contar eventos emitidos. Deve ser 1 (ou 2 se houver retry interno).
249
+ 2. **Atributos canônicos presentes** — checar `user.id`, `tenant_id`, `request.id`, `result.success`, `endpoint`, `duration_ms` no evento emitido.
250
+ 3. **Alta cardinalidade verificada** — `select count(distinct user_id)` deve crescer com tráfego real (não estagnar em N pequeno).
251
+ 4. **`result.success` define SLI** — boolean confiável para alimentar SLO downstream (ver skill `event-based-slos`).
252
+ 5. **Erros têm `error.type` enum** — não `error.message` cru. Permite group by por categoria.
253
+ 6. **Build_id presente** — permite comparar versão antes vs depois de deploy.
254
+ 7. **Smoke local** — emitir 100 eventos sintéticos, queryar via `select * from events where user_id = X` deve retornar todos.
255
+
256
+ ---
257
+
258
+ ## Ver também
259
+
260
+ - `kit/skills/_shared-observability/glossary.md` — termos canônicos, campos canônicos, anti-patterns
261
+ - `kit/skills/distributed-tracing/SKILL.md` — como spans se conectam em traces
262
+ - `kit/skills/opentelemetry-standard/SKILL.md` — SDK e exporters
263
+ - `kit/skills/core-analysis-loop/SKILL.md` — como queryar wide events para debug
264
+
265
+ *Material-fonte: Observability Engineering (O'Reilly, 2022) — Cap 5: "Structured Events Are the Building Blocks of Observability".*