Todo pipeline que gera conteúdo com apoio de IA em escala corre um risco específico: o Google Search trata conteúdo em escala sem valor humano agregado como spam, independente de ter sido produzido por automação, por humano, ou pelos dois. É a política oficial de Spam Policies for Google Web Search. Não é usar IA que é punido. É publicar sem revisão que agregue valor real ao leitor.
Foi essa a linha que guiou a arquitetura deste próprio blog: um pipeline no N8N que gera rascunho via LLM, mas nunca publica sozinho. Cada post passa por um gate humano (eu) antes de ir ao ar. Este post é literalmente esse pipeline descrevendo a si mesmo.
Por que um gate humano obrigatório
A decisão foi deliberada: cadência de 2 a 4 posts por semana, não publicação diária full-automática. Isso reduz o volume que um único revisor consegue realmente avaliar com atenção, e é exatamente esse atrito que separa “IA-assistido com revisão” de “conteúdo em escala”.
O gate não é burocracia. É a peça que decide se o domínio inteiro fica de pé no ranqueamento ou não.
Na prática, o gate vive como Pull Request no GitHub. O N8N escreve o markdown, abre o PR, o Cloudflare Pages gera um preview deploy, e eu recebo o aviso no Telegram. Aprovar é simplesmente dar merge; rejeitar é fechar o PR ou pedir ajuste.
A arquitetura em 4 passos
- O N8N solicita o rascunho para a LLM API a partir de um tema/prompt que eu disparo.
- O N8N cria uma branch, escreve o markdown em
src/content/blog/pt/<slug>.mde abre o Pull Request. - O GitHub dispara um preview deploy no Cloudflare Pages, publicado como check run no próprio PR; o Telegram me avisa com o link.
- Eu reviso o preview e decido: merge (publica) ou fecho o PR (descarto ou peço ajuste).
O contrato de schema que protege a produção
O acoplamento entre o N8N e o Astro é exatamente um schema Zod validado no build. Um markdown malformado nunca chega à produção: o build do preview falha antes.
// Schema mínimo validado no build: contrato N8N -> Astro
const blogSchema = z.object({
title: z.string().min(10).max(70),
description: z.string().min(50).max(160),
pubDate: z.coerce.date(),
category: z.enum(['tech', 'ia', 'automacao']),
draft: z.boolean().default(false),
});
O schema real, com todos os campos (tags validadas contra uma lista fechada, redirectFrom para redirecionar slugs antigos, translationKey para o par PT/EN futuro), vive em src/content/post-schema.mjs deste próprio repositório.
Um detalhe que vale registrar: essa mesma regra existe em dois lugares (o Zod do Astro e uma réplica em JavaScript dentro do N8N, que valida o frontmatter antes de sequer abrir o PR). Duas fontes de verdade para a mesma regra são um risco real de divergência silenciosa, então o build também roda um teste de contrato que passa o mesmo conjunto de payloads pelos dois validadores e falha se eles discordarem em qualquer caso.
O que eu mudaria hoje
Duas coisas ficaram claras montando o pipeline. Primeiro, um botão de aprovação inline no Telegram (que chamaria um webhook e faria o merge via API, sem abrir o GitHub) economizaria tempo real, mas ainda é etapa avançada, não o padrão inicial: o merge manual pelo GitHub mobile já cumpre o gate com uma fricção aceitável. Segundo, o preview deploy é o único ponto em que eu de fato leio o post inteiro antes de publicar; é ali que o gate humano realmente acontece, não no clique de merge em si.
Se você está montando algo parecido, o ponto que eu não pularia é o schema validado no build. É a diferença entre um erro de formatação virar um post publicado quebrado, ou nunca sair do preview.


