@luanpdd/kit-mcp 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +58 -0
- package/gates/ai-prompt-stability.md +120 -0
- package/gates/golden-signals-coverage.md +133 -0
- package/gates/legacy-refactor-safety.md +178 -0
- package/gates/observability-coverage.md +151 -0
- package/gates/postmortem-template-required.md +127 -0
- package/gates/prr-checklist-coverage.md +128 -0
- package/gates/release-pipeline-policy.md +132 -0
- package/kit/COMANDOS.md +15 -0
- package/kit/agents/ai-mutation-tester.md +298 -0
- package/kit/agents/cascading-failures-auditor.md +306 -0
- package/kit/agents/executor.md +13 -0
- package/kit/agents/golden-signals-instrumenter.md +241 -0
- package/kit/agents/legacy-characterizer.md +378 -0
- package/kit/agents/load-shedding-instrumenter.md +297 -0
- package/kit/agents/observability-coverage-auditor.md +325 -0
- package/kit/agents/omm-auditor.md +99 -0
- package/kit/agents/payload-capture-instrumenter.md +283 -0
- package/kit/agents/planner.md +29 -0
- package/kit/agents/postmortem-writer.md +282 -0
- package/kit/agents/prr-conductor.md +296 -0
- package/kit/agents/refactor-safety-auditor.md +414 -0
- package/kit/agents/release-pipeline-auditor.md +360 -0
- package/kit/agents/seam-finder.md +367 -0
- package/kit/agents/shotgun-surgery-detector.md +359 -0
- package/kit/agents/storytelling-analyst.md +309 -0
- package/kit/agents/supabase-architect.md +49 -0
- package/kit/agents/supabase-edge-fn-writer.md +114 -0
- package/kit/agents/supabase-migration-writer.md +80 -0
- package/kit/agents/supabase-storage-implementer.md +156 -0
- package/kit/agents/toil-auditor.md +277 -0
- package/kit/agents/verifier.md +30 -0
- package/kit/commands/auditar-cascading.md +111 -0
- package/kit/commands/auditar-marco.md +124 -1
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -0
- package/kit/commands/auditar-refactor.md +219 -0
- package/kit/commands/auditar-release.md +109 -0
- package/kit/commands/auditar-toil.md +129 -0
- package/kit/commands/capturar-payloads.md +193 -0
- package/kit/commands/caracterizar-prompt.md +195 -0
- package/kit/commands/caracterizar.md +212 -0
- package/kit/commands/concluir-marco.md +95 -1
- package/kit/commands/detectar-duplicacao.md +197 -0
- package/kit/commands/discutir-fase.md +41 -0
- package/kit/commands/encontrar-seams.md +136 -0
- package/kit/commands/forense.md +103 -1
- package/kit/commands/golden-signals.md +142 -0
- package/kit/commands/legacy.md +263 -0
- package/kit/commands/load-shedding.md +117 -0
- package/kit/commands/observabilidade.md +2 -0
- package/kit/commands/postmortem.md +179 -0
- package/kit/commands/prr.md +205 -0
- package/kit/commands/refactor-seguro.md +321 -0
- package/kit/commands/risk-budget.md +220 -0
- package/kit/commands/sre.md +230 -0
- package/kit/commands/storytelling.md +179 -0
- package/kit/skills/_shared-legacy/glossary.md +389 -0
- package/kit/skills/_shared-sre/glossary.md +712 -0
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -0
- package/kit/skills/blameless-postmortems/SKILL.md +340 -0
- package/kit/skills/cascading-failures/SKILL.md +307 -0
- package/kit/skills/eliminating-toil/SKILL.md +243 -0
- package/kit/skills/event-based-slos/SKILL.md +22 -0
- package/kit/skills/four-golden-signals/SKILL.md +314 -0
- package/kit/skills/hermetic-builds/SKILL.md +323 -0
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -0
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -0
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -0
- package/kit/skills/legacy-extract-class/SKILL.md +203 -0
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -0
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -0
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -0
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -0
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -0
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -0
- package/kit/skills/llm-as-dependency/SKILL.md +436 -0
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -0
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -0
- package/kit/skills/production-readiness-review/SKILL.md +305 -0
- package/kit/skills/release-engineering/SKILL.md +367 -0
- package/kit/skills/retry-strategies/SKILL.md +372 -0
- package/kit/skills/sre-risk-management/SKILL.md +221 -0
- package/package.json +2 -2
|
@@ -142,6 +142,17 @@ projeto: {project_id ou "novo"} · tier: {tier} · gerado em {timestamp}
|
|
|
142
142
|
`/supabase migration` para iniciar Wave 1.
|
|
143
143
|
`/supabase rls` para Wave 2.
|
|
144
144
|
...
|
|
145
|
+
|
|
146
|
+
## 9. Observabilidade
|
|
147
|
+
{tabela `obs.events` + audit triggers + SLI views — gerada pelo bloco "Observabilidade integrada"}
|
|
148
|
+
|
|
149
|
+
## 10. PRR pré-production
|
|
150
|
+
Antes de aceitar tráfego real (≥ 1% de usuários), conduzir Production Readiness Review:
|
|
151
|
+
- Invocar `/sre prr --service <nome>` ou `/prr --feature <descrição>` (cross-ref [prr-conductor](./prr-conductor.md))
|
|
152
|
+
- 6 axes obrigatórios: System Architecture, Instrumentation/Metrics/Monitoring, Emergency Response, Capacity Planning, Change Management, Performance
|
|
153
|
+
- Engagement model: Simple (serviços pequenos), Early Engagement (críticos), Frameworks (built on platform)
|
|
154
|
+
- Gaps P0 = blocker (sem instrumentação básica, sem rollback, sem on-call); Gaps P1 = scheduled tasks
|
|
155
|
+
- Reviewer ≠ time dev — par externo ou SRE conduz (anti auto-PRR)
|
|
145
156
|
```
|
|
146
157
|
|
|
147
158
|
Sem preâmbulo. Sem "vou analisar agora". O caller precisa do plano para delegar.
|
|
@@ -164,3 +175,41 @@ Schema nasce com observabilidade — não é addon. Este agent SEMPRE projeta:
|
|
|
164
175
|
**Output adicionado:** seção "## 9. Observabilidade" no plano com tabela de `obs.events` + audit triggers + SLI views.
|
|
165
176
|
|
|
166
177
|
**Validação ODD** (skill [`observability-driven-development`](../skills/observability-driven-development/SKILL.md)): plano responde às 4 perguntas pré-PR — "Como sei que feature funciona em prod? Como comparo versões? Como sei quem está usando? Como detecto anomalias?"
|
|
178
|
+
|
|
179
|
+
## Production Readiness Review
|
|
180
|
+
|
|
181
|
+
> Cross-ref canônico: [production-readiness-review](../skills/production-readiness-review/SKILL.md) (cap 32 do livro Google SRE — Evolving SRE Engagement Model). Para conduzir o PRR de fato, delegar para [prr-conductor](./prr-conductor.md).
|
|
182
|
+
|
|
183
|
+
Schema + RLS + Edge Functions Supabase **NÃO são production-ready** só por estarem corretos — production-readiness é evidence-based, com gate explícito em 6 axes. Este agent **SEMPRE** sugere PRR no plano (seção `## 10. PRR pré-production` do output) — sem exceção.
|
|
184
|
+
|
|
185
|
+
### 6 axes obrigatórios
|
|
186
|
+
|
|
187
|
+
| Axe | O que verifica em contexto Supabase |
|
|
188
|
+
|---|---|
|
|
189
|
+
| **System Architecture** | Redundância (RLS isolamento por tenant; reverso de migrations testado), SPOFs mapeados (single project Supabase = SPOF — branches Pro mitigam), graceful degradation |
|
|
190
|
+
| **Instrumentation / Metrics / Monitoring** | 4 golden signals em Edge Functions (cross-ref [supabase-edge-fn-writer](./supabase-edge-fn-writer.md)), `obs.events` populada, audit hooks ativos, SLI/SLO definidos por jornada crítica |
|
|
191
|
+
| **Emergency Response** | Runbook de incident (RLS broken, schema corrupt, Edge Function 5xx storm), on-call rotation, postmortem template em `.planning/postmortems/` |
|
|
192
|
+
| **Capacity Planning** | Spend Cap configurado, branch billing entendido (Pro), egress projetado, pgvector index size estimate, Edge concurrent invocations limite |
|
|
193
|
+
| **Change Management** | Migrations declarative + reverso testado, RLS policies versionadas em git, Edge Function rollback strategy, supabase functions deploy --import-map idempotente |
|
|
194
|
+
| **Performance** | Load test report (RPS sustentado), p99 latency baseline, RLS policy explain plan (sem seq scan em filtro), index coverage |
|
|
195
|
+
|
|
196
|
+
### 3 engagement models (escolher conforme criticidade)
|
|
197
|
+
|
|
198
|
+
- **Simple PRR** — para serviços internos / dogfooding / staging-only. Checklist com signoff Eng Lead. Custo baixo, cobertura básica.
|
|
199
|
+
- **Early Engagement** — para serviços tier-1 (production-bound, user-facing, paid tier). PRR conduzido por SRE/external com 6 axes review profundo. **Default para Edge Functions user-facing**.
|
|
200
|
+
- **Frameworks / SRE Platform** — para múltiplos serviços built on top de plataforma comum (ex: framework interno que outros times usam). PRR uma vez por plataforma, depois auto-herança para serviços novos.
|
|
201
|
+
|
|
202
|
+
### Quando re-rodar PRR
|
|
203
|
+
|
|
204
|
+
- Após mudança maior (rewrite, novo dependency externo, RPS 10×, nova RLS strategy)
|
|
205
|
+
- Antes de aumentar tráfego cross-tier (free → paid → enterprise)
|
|
206
|
+
- Re-run anual mesmo sem mudança (entropia operacional)
|
|
207
|
+
|
|
208
|
+
> **PRR NÃO é one-shot** — statement "passou PRR uma vez em 2024" não é evidence em 2026.
|
|
209
|
+
|
|
210
|
+
### Anti-patterns prevenidos
|
|
211
|
+
|
|
212
|
+
- Auto-PRR pelo time dev → SEMPRE par externo ou SRE conduz (eyes-on-code novos)
|
|
213
|
+
- "Deploy primeiro, PRR depois" → SEMPRE PRR ANTES de aceitar tráfego real (≥ 1% users)
|
|
214
|
+
- Pular axe (ex: ignorar Capacity Planning porque "feature é small") → SEMPRE 6 axes; pular 1 = aprovação inválida (lacuna oculta vira incident em 6 meses)
|
|
215
|
+
- "Acreditamos que está pronto" → SEMPRE evidence-based (load test report, runbook URL, dashboard link)
|
|
@@ -23,6 +23,18 @@ Você é o Edge Function writer Supabase. Recebe descrição de função (endpoi
|
|
|
23
23
|
|
|
24
24
|
Edge Functions têm pegadinhas específicas do Deno runtime que diferem de Node: bare specifiers quebram, env vars têm nomes pre-populados, file writes só em `/tmp`, multi-rota precisa de prefix. Este agent garante que cada função seguirá essas regras desde o primeiro commit.
|
|
25
25
|
|
|
26
|
+
**v1.12 — Adicional Legacy:** Edge Functions são **canônicas para o "API-only application" pattern** (cap 15 livro Feathers, modernizado). Quando este agent escreve Edge Function que wrappar API externa (Stripe/OpenAI/Twilio/etc), aplica skill [`legacy-api-only-applications`](../skills/legacy-api-only-applications/SKILL.md) — adapter pattern com interface mínima testável + anti-corruption layer + fake provider para tests. Quando detecta uso de LLM client (OpenAI/Anthropic), aplica skill [`llm-as-dependency`](../skills/llm-as-dependency/SKILL.md) — LLMProvider interface + adapter por vendor + FakeLLMProvider. Por padrão, este agent oferece **payload capture pattern** (skill [`pre-refactor-characterization`](../skills/pre-refactor-characterization/SKILL.md) Pattern 7) — instrumentação dedicada controlada por env `CAPTURE_PAYLOADS` para captura de fixtures reais via `mcp__supabase__get_logs`.
|
|
27
|
+
|
|
28
|
+
**v1.11 — Adicional SRE Resilience:** Toda Edge Function gerada inclui por padrão **defesas de cascade** (skills `cascading-failures`, `retry-strategies`, `load-shedding-graceful-degradation`):
|
|
29
|
+
|
|
30
|
+
1. **Timeout em chamadas externas** — `AbortSignal.timeout(2000)` por default
|
|
31
|
+
2. **Retry com full jitter** — `delayMs = Math.random() * baseMs * 2^attempt`; max 3 retries; cap 30s
|
|
32
|
+
3. **Deadline propagation** — handler parsea `x-deadline-ms` header e passa downstream
|
|
33
|
+
4. **Server-side load shedding** — `LoadShedder` em `_shared/load-shedder.ts`; 503 + Retry-After quando saturated
|
|
34
|
+
5. **Idempotency key** — em writes; gerada via UUID se cliente não enviar
|
|
35
|
+
|
|
36
|
+
Sem flag explícita, esses patterns são incluídos no template de Edge Function nova. Para legacy (Edge Functions já escritas), invocar `/auditar-cascading <fn>` + `/load-shedding <fn>` para retrofit.
|
|
37
|
+
|
|
26
38
|
## Inputs esperados (do caller)
|
|
27
39
|
|
|
28
40
|
- `function_name`: nome da função (kebab-case, ex: `process-emails`, `generate-embeddings`)
|
|
@@ -196,6 +208,106 @@ Edge Function nasce instrumentada com OTel — não é addon. Beneficia mais que
|
|
|
196
208
|
|
|
197
209
|
**Output adicionado:** template completo de Edge Function inclui SDK setup + span wrapper + propagação outbound + classificador de error.type. ODD-compliant (4 perguntas pré-PR endereçadas).
|
|
198
210
|
|
|
211
|
+
## Four Golden Signals
|
|
212
|
+
|
|
213
|
+
> Cross-ref canônico: [four-golden-signals](../skills/four-golden-signals/SKILL.md) (cap 6 do livro Google SRE — Monitoring Distributed Systems). Para retro-instrumentar Edge Function existente, delegar para [golden-signals-instrumenter](./golden-signals-instrumenter.md).
|
|
214
|
+
|
|
215
|
+
Edge Function user-facing nasce com os 4 sinais dourados — não é addon. O bloco `## Observabilidade integrada` acima cobre OTel SDK + spans + propagation; este bloco especifica os **4 instrumentos canônicos** que o template gerado SEMPRE inclui:
|
|
216
|
+
|
|
217
|
+
| Signal | Instrumento | Dimensão | Valor padrão |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| **Latency** | `meter.createHistogram('http_request_duration_ms')` com `explicitBucketBoundaries: [1,2,5,10,25,50,100,250,500,1000,2500,5000,10000,30000]` | `result=success\|error` (separar success de erro) | Bucketing exponencial captura long tail sem cardinality explosion |
|
|
220
|
+
| **Traffic** | `meter.createCounter('http_requests_total')` | `endpoint`, `http_method` | Incrementado antes de processar request |
|
|
221
|
+
| **Errors** | `meter.createCounter('http_errors_total')` | `error.type` enum (5-15 valores: `timeout\|validation\|auth\|rate_limit\|db\|provider_down\|...`) — **nunca** `error.message` (cardinalidade explode) | Incrementado em catch + path 4xx/5xx |
|
|
222
|
+
| **Saturation** | `meter.createObservableGauge('saturation_pct')` com callback que lê estado real | resource-specific: `connection_pool` (pg) / `concurrency_limit` (Edge runtime) / `egress_bandwidth` / `cache_memory` | % do recurso mais escasso identificado ANTES de instrumentar |
|
|
223
|
+
|
|
224
|
+
### Snippet canônico — adicionado ao topo do `index.ts` gerado
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
// PT-BR: 4 golden signals — instrumentação mínima universal
|
|
228
|
+
import { metrics } from 'npm:@opentelemetry/api@1.9.0'
|
|
229
|
+
const meter = metrics.getMeter('<function_name>')
|
|
230
|
+
|
|
231
|
+
// 1. LATENCY — histogram bucketed exponencial
|
|
232
|
+
const latencyHistogram = meter.createHistogram('http_request_duration_ms', {
|
|
233
|
+
description: 'Edge function latency split by result (success vs error)',
|
|
234
|
+
unit: 'ms',
|
|
235
|
+
advice: { explicitBucketBoundaries: [1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000] }
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// 2. TRAFFIC — counter de requests recebidos
|
|
239
|
+
const trafficCounter = meter.createCounter('http_requests_total', {
|
|
240
|
+
description: 'Total HTTP requests received by edge function'
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// 3. ERRORS — counter por error.type (NUNCA error.message — cardinalidade)
|
|
244
|
+
const errorsCounter = meter.createCounter('http_errors_total', {
|
|
245
|
+
description: 'Edge function errors by error.type enum'
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// 4. SATURATION — gauge do recurso mais escasso (callback lê estado real)
|
|
249
|
+
// PT-BR: para Edge Function default, saturation = concurrency_limit_used %
|
|
250
|
+
// Substituir callback conforme recurso identificado (db pool, queue, cache)
|
|
251
|
+
meter.createObservableGauge('saturation_pct', {
|
|
252
|
+
description: 'Saturation of scarcest resource — function-specific'
|
|
253
|
+
}).addCallback((result) => {
|
|
254
|
+
// PT-BR: callback canônico — ler estado real (ex: SELECT count(*) FROM pg_stat_activity)
|
|
255
|
+
// Aqui placeholder: 0 < value < 1
|
|
256
|
+
result.observe(getSaturationPct()) // implementar conforme resource
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Wrapping no handler
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
Deno.serve(async (req: Request) => {
|
|
264
|
+
const start = performance.now()
|
|
265
|
+
const endpoint = new URL(req.url).pathname
|
|
266
|
+
trafficCounter.add(1, { endpoint, http_method: req.method })
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const response = await handle(req)
|
|
270
|
+
latencyHistogram.record(performance.now() - start, {
|
|
271
|
+
endpoint,
|
|
272
|
+
result: response.ok ? 'success' : 'error',
|
|
273
|
+
})
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
errorsCounter.add(1, { endpoint, 'error.type': classifyError(response) })
|
|
276
|
+
}
|
|
277
|
+
return response
|
|
278
|
+
} catch (err) {
|
|
279
|
+
latencyHistogram.record(performance.now() - start, { endpoint, result: 'error' })
|
|
280
|
+
errorsCounter.add(1, { endpoint, 'error.type': classifyError(err) })
|
|
281
|
+
throw err
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// PT-BR: classifyError DEVE retornar enum fechado, não err.message
|
|
286
|
+
function classifyError(e: unknown): string {
|
|
287
|
+
if (e instanceof TimeoutError) return 'timeout'
|
|
288
|
+
if (e instanceof ValidationError) return 'validation'
|
|
289
|
+
if (e instanceof AuthError) return 'auth'
|
|
290
|
+
// ... 5-15 valores no total
|
|
291
|
+
return 'unknown'
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Saturation por tipo de Edge Function
|
|
296
|
+
|
|
297
|
+
| Tipo de função | Recurso mais escasso | Implementação típica |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| API simples (GET/POST com leitura DB) | `pg_pool` connections used | `select count(*) from pg_stat_activity where state = 'active'` |
|
|
300
|
+
| RAG / embeddings | `concurrency_limit` (provider externo) | counter de requests in-flight |
|
|
301
|
+
| Email / queue consumer (cron → pgmq) | `pgmq.queue_length` | `select msg_count from pgmq.metrics_<queue>` |
|
|
302
|
+
| Storage I/O heavy (uploads grandes) | `egress_bandwidth` | bytes-out tracker em window |
|
|
303
|
+
|
|
304
|
+
### Anti-patterns prevenidos
|
|
305
|
+
|
|
306
|
+
- Errors counter usando `error.type = err.message` → SEMPRE enum fechado (5-15 valores)
|
|
307
|
+
- Latency mistura success + error → SEMPRE `result` dimension separa
|
|
308
|
+
- Mean latency em vez de histogram → SEMPRE histogram com percentis derivados em backend
|
|
309
|
+
- Saturation genérico (CPU%) sem identificar recurso real → SEMPRE escolher recurso scarcest da função
|
|
310
|
+
|
|
199
311
|
## Ver também
|
|
200
312
|
|
|
201
313
|
- [supabase-edge-functions](../skills/supabase-edge-functions/SKILL.md) — base de conhecimento canônica
|
|
@@ -205,3 +317,5 @@ Edge Function nasce instrumentada com OTel — não é addon. Beneficia mais que
|
|
|
205
317
|
- [distributed-tracing](../skills/distributed-tracing/SKILL.md) — context propagation
|
|
206
318
|
- [structured-events](../skills/structured-events/SKILL.md) — campos canônicos
|
|
207
319
|
- [observability-driven-development](../skills/observability-driven-development/SKILL.md) — 4 perguntas pré-PR
|
|
320
|
+
- [four-golden-signals](../skills/four-golden-signals/SKILL.md) — 4 sinais canônicos (Latency, Traffic, Errors, Saturation) cap 6 livro Google SRE
|
|
321
|
+
- [golden-signals-instrumenter](./golden-signals-instrumenter.md) — agent que retro-instrumenta Edge Functions existentes com os 4 signals
|
|
@@ -172,3 +172,83 @@ Toda migration emite evento estruturado e cria audit hooks por default — não
|
|
|
172
172
|
3. **Atributos canônicos** em qualquer função criada: `set search_path = ''` + comments com `result.success`, `error.type` enum esperado (skill [`structured-events`](../skills/structured-events/SKILL.md)).
|
|
173
173
|
|
|
174
174
|
**Output adicionado:** seção "## Audit hooks" + "## Migration event emit" no SQL gerado, comentadas em PT-BR.
|
|
175
|
+
|
|
176
|
+
## Alerta toil — automação via pg_cron
|
|
177
|
+
|
|
178
|
+
> Cross-ref canônico: [eliminating-toil](../skills/eliminating-toil/SKILL.md) (cap 5 do livro Google SRE — Eliminating Toil). Para auditoria sistemática de toil em todo o repo, delegar para [toil-auditor](./toil-auditor.md).
|
|
179
|
+
|
|
180
|
+
Migrations SQL executadas **manualmente em cadência regular** (rebuild índice, VACUUM, REFRESH MATERIALIZED VIEW, ANALYZE) são toil canônico — passam todos os 6 critérios: manual, repetitivo, automatizável, tático, sem valor durável, escala linear. Este agent **detecta padrões de toil** ao escrever migration e **alerta proativamente** sugerindo automação via `pg_cron`.
|
|
181
|
+
|
|
182
|
+
### 6 critérios — quando uma migration é toil-prone
|
|
183
|
+
|
|
184
|
+
Migration descreve operação que será re-executada > 1× = toil-prone. Aplicar 6 critérios da skill `eliminating-toil`:
|
|
185
|
+
|
|
186
|
+
| Critério | Pergunta | Sinal de toil |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| 1. Manual | Operador roda `psql` ou aplica migration "quando lembra"? | Sim |
|
|
189
|
+
| 2. Repetitivo | Já foi executada 3+ vezes em milestones diferentes? | Sim |
|
|
190
|
+
| 3. Automatizável | `pg_cron` consegue agendar sem julgamento humano? | Sim |
|
|
191
|
+
| 4. Tático | Reage a sintoma (lentidão, bloat, stale view) sem planejar? | Sim |
|
|
192
|
+
| 5. Sem valor durável | Não cria asset permanente — só "limpa" estado | Sim |
|
|
193
|
+
| 6. Escala linear | Mais users / mais dados = mais frequência manual | Sim |
|
|
194
|
+
|
|
195
|
+
Se TODOS os 6 = sim → **toil**. Bloquear migration manual recorrente; oferecer alternativa via `pg_cron`.
|
|
196
|
+
|
|
197
|
+
### Padrões SQL canônicos que SEMPRE disparam alerta toil
|
|
198
|
+
|
|
199
|
+
| Operação manual | Por quê é toil | Automação canônica |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `REINDEX TABLE x` recorrente (a cada N semanas) | Rebuild de bloat de índice é tático, sem valor durável, repetitivo | `select cron.schedule('reindex_x', '0 3 * * 0', $$reindex table x$$);` (semanal 3am) |
|
|
202
|
+
| `VACUUM ANALYZE x` manual | autovacuum não está acompanhando — sintoma de tuning, não fix manual | Tunar `autovacuum_vacuum_scale_factor` para tabela específica + `pg_cron` se necessário |
|
|
203
|
+
| `REFRESH MATERIALIZED VIEW x` manual | Stale view detectada por user reclamação ou alert | `select cron.schedule('refresh_x', '*/30 * * * * *', $$refresh materialized view concurrently x$$);` |
|
|
204
|
+
| `ANALYZE` em tabela após bulk insert manual | Estatísticas desatualizadas após ETL — bem conhecido | Trigger AFTER INSERT/COPY com `analyze` no fim do batch, ou `pg_cron` pós-ETL |
|
|
205
|
+
| `delete from logs where created_at < now() - interval '90d'` manual recorrente | Retention manual = toil clássico | `select cron.schedule('purge_logs', '0 4 * * *', $$delete from logs where ...$$);` |
|
|
206
|
+
| `dump + restore` periódico para estatísticas / planos cache | Operação repetitiva sem valor permanente | `pg_cron` job ou `pg_stat_reset_*()` calls automatizadas |
|
|
207
|
+
|
|
208
|
+
### Snippet canônico — converter manual em pg_cron
|
|
209
|
+
|
|
210
|
+
```sql
|
|
211
|
+
-- PT-BR: ANTES — toil (operador roda manualmente)
|
|
212
|
+
-- $ psql -c 'reindex table heavy_table;' ← repetir a cada 2 semanas
|
|
213
|
+
|
|
214
|
+
-- PT-BR: DEPOIS — automação via pg_cron (necessita extension pg_cron habilitada)
|
|
215
|
+
create extension if not exists pg_cron;
|
|
216
|
+
|
|
217
|
+
select cron.schedule(
|
|
218
|
+
'reindex_heavy_table_biweekly',
|
|
219
|
+
'0 3 1,15 * *', -- 3am dias 1 e 15
|
|
220
|
+
$$ reindex table public.heavy_table $$
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
-- PT-BR: monitor — falha em job pg_cron emite linha em cron.job_run_details
|
|
224
|
+
-- alimentar alerta SLO se job falha 3+ vezes seguidas
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Quando NÃO automatizar (não é toil)
|
|
228
|
+
|
|
229
|
+
- **Migration de schema (DDL one-shot)** — `create table`, `alter table add column` são project work, não toil. Não recorrentes.
|
|
230
|
+
- **Backfill data único** — `update orders set status = ...` aplicado 1× para corrigir bug é grungy work, não toil.
|
|
231
|
+
- **Rebuild que requer julgamento** — `reindex` que requer escolher hora baseada em load patterns variáveis, ou que precisa coordenação com release. Mantém manual mas documenta runbook.
|
|
232
|
+
|
|
233
|
+
### Output do agent — adicionado ao SQL gerado
|
|
234
|
+
|
|
235
|
+
Quando o agent detecta que a migration descreve operação toil-prone (regex em DDL: `reindex|vacuum|refresh materialized|delete from .* interval`), adiciona comentário-alerta no header do arquivo SQL gerado:
|
|
236
|
+
|
|
237
|
+
```sql
|
|
238
|
+
/*
|
|
239
|
+
⚠ TOIL ALERT — esta operação parece recorrente.
|
|
240
|
+
|
|
241
|
+
Se será executada em cadência regular, considere automação via pg_cron:
|
|
242
|
+
select cron.schedule('<job_name>', '<schedule>', $$ <sql> $$);
|
|
243
|
+
|
|
244
|
+
Cross-ref: kit/skills/eliminating-toil/SKILL.md (6 critérios canônicos)
|
|
245
|
+
kit/agents/toil-auditor.md (audit sistemático para repo todo)
|
|
246
|
+
*/
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Anti-patterns prevenidos
|
|
250
|
+
|
|
251
|
+
- "Roda quando der" runbook → SEMPRE pg_cron + monitoring de falha do job
|
|
252
|
+
- `pg_cron` schedule mas sem alerta de falha → SEMPRE incluir SLO em `cron.job_run_details` (% sucesso 30d)
|
|
253
|
+
- Automação parcial (script humano-iniciado) → ainda é toil (humano pressiona botão); preferir cron.schedule completo
|
|
254
|
+
- Migration manual recorrente "porque é só uma vez por mês" → 12×/ano = toil, regra ≤ 50% se acumular vários "só um por mês"
|
|
@@ -249,6 +249,160 @@ Upload events são quentes em custo (egress + storage) e em UX (lentidão de upl
|
|
|
249
249
|
|
|
250
250
|
**Output adicionado:** seção "## Observability hooks" com snippet de upload/download wrapper.
|
|
251
251
|
|
|
252
|
+
## Saturation signal — bucket size + quota
|
|
253
|
+
|
|
254
|
+
> Cross-ref canônico: [four-golden-signals](../skills/four-golden-signals/SKILL.md) (cap 6 do livro Google SRE — Monitoring Distributed Systems). Para retro-instrumentar storage existente com os 4 signals, delegar para [golden-signals-instrumenter](./golden-signals-instrumenter.md).
|
|
255
|
+
|
|
256
|
+
Storage tem o **recurso mais escasso explícito**: o quota do plano (Free 1 GB, Pro 100 GB, Team 1 TB, etc.). Sem signal de saturation, time descobre quota exhaustion via incident (uploads falham silenciosamente em UX) — **anti-pattern clássico** de white-box monitoring sem detecção precoce. O bloco `## Observabilidade integrada` acima cobre Latency / Traffic / Errors (3 signals); este bloco completa com **Saturation** — o 4º signal canônico.
|
|
257
|
+
|
|
258
|
+
### Saturation = bucket size ÷ quota plan
|
|
259
|
+
|
|
260
|
+
| Plano | Quota total | Threshold ALERT (yellow) | Threshold PAGE (red) |
|
|
261
|
+
|---|---|---|---|
|
|
262
|
+
| Free | 1 GB | 80% (800 MB) | 95% (950 MB) |
|
|
263
|
+
| Pro | 100 GB | 80% (80 GB) | 95% (95 GB) |
|
|
264
|
+
| Team | 1 TB | 80% (800 GB) | 95% (950 GB) |
|
|
265
|
+
| Enterprise | custom | custom | custom |
|
|
266
|
+
|
|
267
|
+
### Signal 1 — Gauge: bucket size atual (bytes)
|
|
268
|
+
|
|
269
|
+
`ObservableGauge` (push periódico via callback) mede tamanho real de cada bucket. Callback consulta `storage.objects` agregado:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
// PT-BR: 4º signal — saturation (gauge de bucket size em bytes)
|
|
273
|
+
import { metrics } from 'npm:@opentelemetry/api@1.9.0'
|
|
274
|
+
const meter = metrics.getMeter('supabase-storage')
|
|
275
|
+
|
|
276
|
+
meter.createObservableGauge('storage_bucket_bytes', {
|
|
277
|
+
description: 'Tamanho atual em bytes por bucket — saturation signal',
|
|
278
|
+
unit: 'bytes',
|
|
279
|
+
}).addCallback(async (result) => {
|
|
280
|
+
// PT-BR: query agregada (rodar via service-role client em cron)
|
|
281
|
+
const sizes = await supabaseAdmin.rpc('storage_bucket_sizes_bytes')
|
|
282
|
+
// expected: [{ bucket_id: 'avatars', total_bytes: 12345678 }, ...]
|
|
283
|
+
for (const row of sizes ?? []) {
|
|
284
|
+
result.observe(row.total_bytes, { 'bucket.id': row.bucket_id })
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
meter.createObservableGauge('storage_saturation_pct', {
|
|
289
|
+
description: 'Saturation = bucket size / quota plan — % do quota usado',
|
|
290
|
+
unit: '1', // ratio (0..1)
|
|
291
|
+
}).addCallback(async (result) => {
|
|
292
|
+
const sizes = await supabaseAdmin.rpc('storage_bucket_sizes_bytes')
|
|
293
|
+
const QUOTA_BYTES = Number(Deno.env.get('SUPABASE_PLAN_QUOTA_BYTES') ?? 1_000_000_000) // default Free
|
|
294
|
+
for (const row of sizes ?? []) {
|
|
295
|
+
result.observe(row.total_bytes / QUOTA_BYTES, { 'bucket.id': row.bucket_id })
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
SQL helper para o callback:
|
|
301
|
+
|
|
302
|
+
```sql
|
|
303
|
+
-- PT-BR: function que retorna bytes por bucket — chamada por callback OTel
|
|
304
|
+
create or replace function public.storage_bucket_sizes_bytes()
|
|
305
|
+
returns table (bucket_id text, total_bytes bigint)
|
|
306
|
+
language sql
|
|
307
|
+
security definer
|
|
308
|
+
set search_path = ''
|
|
309
|
+
as $$
|
|
310
|
+
select bucket_id, coalesce(sum((metadata->>'size')::bigint), 0) as total_bytes
|
|
311
|
+
from storage.objects
|
|
312
|
+
group by bucket_id;
|
|
313
|
+
$$;
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Signal 2 — Counter: quota near-exhaustion events
|
|
317
|
+
|
|
318
|
+
`Counter` incrementa a cada upload que **detecta** approach a quota threshold (80%, 95%). Permite contar eventos críticos para alerting:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
// PT-BR: counter incrementado em cada upload
|
|
322
|
+
const quotaWarnings = meter.createCounter('storage_quota_warnings_total', {
|
|
323
|
+
description: 'Counter de eventos onde upload aproxima quota — alimentar alert SLO',
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
export async function uploadInstrumented(file: File, filename: string) {
|
|
327
|
+
const supabase = createClient()
|
|
328
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
329
|
+
if (!user) throw new Error('not authenticated')
|
|
330
|
+
|
|
331
|
+
const path = `${user.id}/${filename}`
|
|
332
|
+
|
|
333
|
+
// PT-BR: pre-check — saturation atual antes de upload
|
|
334
|
+
const sizes = await supabaseAdmin.rpc('storage_bucket_sizes_bytes')
|
|
335
|
+
const bucketSize = sizes?.find(s => s.bucket_id === '<bucket_name>')?.total_bytes ?? 0
|
|
336
|
+
const QUOTA = Number(Deno.env.get('SUPABASE_PLAN_QUOTA_BYTES') ?? 1_000_000_000)
|
|
337
|
+
const saturation = bucketSize / QUOTA
|
|
338
|
+
|
|
339
|
+
if (saturation >= 0.95) {
|
|
340
|
+
quotaWarnings.add(1, { 'bucket.id': '<bucket_name>', threshold: '95pct' })
|
|
341
|
+
} else if (saturation >= 0.80) {
|
|
342
|
+
quotaWarnings.add(1, { 'bucket.id': '<bucket_name>', threshold: '80pct' })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const { data, error } = await supabase.storage
|
|
346
|
+
.from('<bucket_name>')
|
|
347
|
+
.upload(path, file, { upsert: true })
|
|
348
|
+
|
|
349
|
+
if (error) throw error
|
|
350
|
+
return data.path
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Cron schedule sugerido
|
|
355
|
+
|
|
356
|
+
Saturation gauge não precisa rodar em cada request — agendar leitura via `pg_cron` (ou OTel SDK polling interval = 60s) é suficiente:
|
|
357
|
+
|
|
358
|
+
```sql
|
|
359
|
+
-- PT-BR: refresh saturation cache a cada 60s para gauge OTel
|
|
360
|
+
create materialized view if not exists obs.storage_saturation as
|
|
361
|
+
select bucket_id, sum((metadata->>'size')::bigint) as total_bytes, now() as captured_at
|
|
362
|
+
from storage.objects
|
|
363
|
+
group by bucket_id;
|
|
364
|
+
|
|
365
|
+
select cron.schedule(
|
|
366
|
+
'refresh_storage_saturation',
|
|
367
|
+
'* * * * *', -- a cada 1 min
|
|
368
|
+
$$ refresh materialized view concurrently obs.storage_saturation $$
|
|
369
|
+
);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Alert SLO sobre saturation
|
|
373
|
+
|
|
374
|
+
Saturation alimenta SLO event-based — não threshold direto:
|
|
375
|
+
|
|
376
|
+
```yaml
|
|
377
|
+
# PT-BR: SLO sobre quota — % de tempo em yellow ou worse
|
|
378
|
+
slo:
|
|
379
|
+
name: storage_quota_healthy
|
|
380
|
+
target: 0.99 # 99% do tempo em < 80% quota
|
|
381
|
+
window: 30d_sliding
|
|
382
|
+
sli:
|
|
383
|
+
type: event_based
|
|
384
|
+
good_event:
|
|
385
|
+
saturation_pct: { lt: 0.80 }
|
|
386
|
+
bad_event:
|
|
387
|
+
saturation_pct: { gte: 0.80 }
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Output do agent — adicionado ao SQL/código gerado
|
|
391
|
+
|
|
392
|
+
Quando agent gera bucket privado novo, **sempre inclui**:
|
|
393
|
+
1. Function SQL `storage_bucket_sizes_bytes()` (uma vez por projeto)
|
|
394
|
+
2. Materialized view `obs.storage_saturation` + pg_cron refresh job
|
|
395
|
+
3. Snippet OTel ObservableGauge no código client wrapper
|
|
396
|
+
4. Counter `storage_quota_warnings_total` no upload wrapper
|
|
397
|
+
5. SLO `storage_quota_healthy` em `.planning/slos/<bucket>.yaml`
|
|
398
|
+
|
|
399
|
+
### Anti-patterns prevenidos
|
|
400
|
+
|
|
401
|
+
- Saturation = "% disco do servidor" → SEMPRE saturation = % quota plan (recurso correto)
|
|
402
|
+
- Threshold direto em alerta CPU/memory para capacity → SEMPRE SLO event-based sobre saturation_pct
|
|
403
|
+
- Polling de bucket size em cada request → SEMPRE materialized view + pg_cron refresh + OTel polling 60s
|
|
404
|
+
- Plan quota hardcoded → SEMPRE env var `SUPABASE_PLAN_QUOTA_BYTES` (varia por plano, pode ser sobrescrita em test)
|
|
405
|
+
|
|
252
406
|
## Ver também
|
|
253
407
|
|
|
254
408
|
- [supabase-storage](../skills/supabase-storage/SKILL.md) — base de conhecimento canônica
|
|
@@ -256,3 +410,5 @@ Upload events são quentes em custo (egress + storage) e em UX (lentidão de upl
|
|
|
256
410
|
- [supabase-auth-ssr](../skills/supabase-auth-ssr/SKILL.md) — usuário autenticado obtém `auth.uid()`
|
|
257
411
|
- [structured-events](../skills/structured-events/SKILL.md) — campos canônicos para upload/download events
|
|
258
412
|
- [telemetry-sampling](../skills/telemetry-sampling/SKILL.md) *(Phase 34)* — head-based sampling por size_bytes
|
|
413
|
+
- [four-golden-signals](../skills/four-golden-signals/SKILL.md) — 4 sinais canônicos (Latency, Traffic, Errors, Saturation) cap 6 livro Google SRE — saturation = bucket size / quota plan
|
|
414
|
+
- [golden-signals-instrumenter](./golden-signals-instrumenter.md) — agent que retro-instrumenta storage existente com os 4 signals
|