Prophub Cloud Foundation

Manual — tools/scaffolder

Todo comando da CLI, toda rota da API, e o que cada casca precisa pra rodar localmente.

Absorvido em plat-eng-platform/tools/scaffolder/ pela ADR-0012 (fusão de repos) — plat-eng-scaffolder não tem mais código vivo. stack/unstack e as 4 tools MCP de Blueprint (list_blueprints/get_blueprint_params/preview_stack/submit_stack) saíram do binário nessa mesma janela: o caminho de tenant foi descontinuado (ADR-0012) e será redesenhado sobre o XRD AppPlacement, não retomado como estava. Ver CONTEXT.md (plat-eng-bootstrap) e docs/adr/ADR-0010-colocacao-fora-do-git.mdx.

CLI — os 7 comandos

scaffold detect <repo>
scaffold validate [path]
scaffold bump <versionsKey> --tag <tag> [--path <repo>]
scaffold init <repo> --github <owner/repo> [--force]
scaffold register <repo> --namespace <ns> --image-repository <repo> --image-tag <tag> [--existing-secret <nome>] [--by <login>]
scaffold promote <repo> <tag> [--by <login>] [--reason <texto>]
scaffold status <nome>
ComandoO que fazPrecisa de token?
detectInfere shape/instances/porta a partir do código — só imprime, não escreve nadaNão
validateValida app.yaml — exit 1 se falhar, usável direto em CINão
bumpEscreve/atualiza a tag de versão em app-delivery/charts/<chart>/versions/<versionsKey>.yaml (plat-eng-platform) — só o scaffolder-api ainda usa este caminho (ADR-0013 §D2, não se autopromove); as outras 3 apps saíram dele no Grupo FSim
initGera app.yaml (+ Dockerfile placeholder + CI se faltando) e abre PR — idempotente, não faz nada se já existe e valida (a menos que --force)Sim
registerProjeta o app.yaml num AppPlacement e aplica no cluster (ADR-0011) — não abre PR. --namespace e a imagem (--image-repository/--image-tag) são obrigatórios; a tag precisa ser imutável. --by grava a atribuição em status.changes — sem ele a mudança fica sem autorNão (precisa de credencial de cluster)
promoteTroca só spec.placement.image.tag de um AppPlacement já registrado (ADR-0013 §1) — nunca reprojeta o Contrato. Recusa se o Contrato do commit divergir do que está no recurso, ou se a imagem não existir no OCIRNão (precisa de credencial de cluster)
statusA Esteira de uma Colocação (Grupo B5): Contrato válido? imagem no OCIR? ExternalSecret saudável? Observa e escreve no status do AppPlacement. Sai 1 se alguma etapa reprovouNão (cluster + ARGOCD_BASE_URL/ARGOCD_API_TOKEN)

bump/init recusam rodar sem SCAFFOLD_GITHUB_TOKEN (PAT com contents:write+ pull-requests:write no(s) repo(s) alvo). detect e validate nunca tocam em token nenhum — seguros de rodar em qualquer ambiente, inclusive CI de terceiros.

register é o único que precisa de credencial de cluster, não do GitHub (para escrever; o app.yaml continua vindo de um checkout local). Dentro do cluster ele usa a ServiceAccount do próprio pod; localmente, o KUBECONFIG (senão ~/.kube/config) — e precisa ser um kubeconfig de token estático, como o portável do plat-eng-bootstrap. O que a OCI gera com oci ce cluster create-kubeconfig tem bloco exec, que este cliente recusa de propósito, com mensagem própria, em vez de seguir e tomar 401.

register valida o render antes de escrever — e kubectl não

Antes de aplicar o AppPlacement, o register monta o values que a Composition vai passar ao chart e o valida contra o values.schema.json daquele chart. Erro de render volta síncrono, e nada é escrito no cluster.

Essa defesa é da ferramenta, não do cluster: quem aplica um AppPlacement com kubectl apply contorna a checagem por inteiro — o API server só conhece as regras do XRD, que são mais frouxas que as do chart (exemplo real: env.fromSecret é aceito em qualquer forma pelo XRD, mas o chart prophub-static não tem bloco env e falha no render). Nesse caminho o recurso nasce válido e o problema só aparece como Application Degraded, minutos depois. Registrado nas Consequências da ADR-0011.

API — rotas autenticadas

A maioria exige sessão (requireSession — cookie dashboard_session, OAuth GitHub do próprio dev, 401 sem ela). Exceção: POST /api/ci/promote, autenticado por um ID token OIDC do próprio GitHub Actions — nunca cookie, nunca kubeconfig (ADR-0013 §1) — verificado contra a JWKS pública do GitHub; owner/repo/commit vêm do token, nunca de campo do corpo.

RotaO que faz
GET /api/meusuário logado
POST /api/detectDetecção (shape/instances/porta) de um repo via API do GitHub, com evidências — não escreve nada
POST /api/initOnboarding: gera app.yaml e abre PR no repo da própria aplicação
POST /api/registerRegistro (ADR-0011): projeta app.yaml num AppPlacement e aplica no cluster — não abre PR
GET /api/prs/{owner}/{repo}/{number}status do PR (mergeable, approved, checks)
POST /api/prs/{owner}/{repo}/{number}/approve / .../mergeaprovar/mergear como a pessoa logada, nunca o bot (ADR-0011) — merge reconfirma mergeable no servidor antes de agir
GET /api/argocd/applications?tenant=<slug>Applications do ArgoCD pra um tenant
POST /api/ci/promotePromoção via CI (ADR-0013 §1/§2) — troca só spec.placement.image.tag do AppPlacement do repo do token
GET /api/placementsLista todos os AppPlacement — nome, namespace, shape, imagem, status já observado
PATCH /api/placements/{name}Edita a Colocação (namespace/ingress/resources/scaling/env.existingSecret) — nunca image (é .../promote) nem contract (é /api/register)
POST /api/placements/{name}/promotePromove pela sessão de um membro logado — mesmo motor de /api/ci/promote, requestedBy vem da sessão, nunca do corpo
GET /api/placements/{name}/statusA Esteira de uma Colocação (Grupo B5): Contrato válido? imagem no OCIR? ExternalSecret saudável? últimas mudanças

/api/tenants, /api/blueprints, /api/stacks/*, /api/tenants/{slug}/apps/{app}/edit/*, /api/tenants/{slug}/removal/* e /api/argocd/claims-status não existem mais — eram do caminho de tenant/Blueprint, descontinuado pela ADR-0012.

MCP server e skills dev-facing (ADR-0012)

Mesma casca da API (/mcp em server.ts, mesmo Bun.serve(), não um deployable à parte) — autenticado com o OAuth do próprio dev (GitHub App 4490460, Dynamic Client Registration, RFC 7591), sem SCAFFOLD_GITHUB_TOKEN nem o binário scaffold no ambiente de quem só quer onboardar/instanciar/remover algo.

Conectar (Claude Code)

claude mcp add --transport http scaffolder https://scaffolder-api.prophub.tech/mcp
claude mcp list   # "scaffolder: ✔ Connected" depois do handshake OAuth no navegador

Requer ser membro da org quanthubbr no GitHub (isAuthorized(login), mcp/authz.ts) — quem não for consegue conectar, mas as tools que mutam (ver coluna abaixo) recusam com "não está autorizado a disparar ações que mutam via MCP".

As 3 tools (src/web/mcp/tools.ts)

O comentário no topo do arquivo ainda fala em "sete tools" (C1: list_blueprints/ get_blueprint_params/preview_stack/submit_stack, embrulhando rotas de tenant/Blueprint que já existiam antes da ADR-0012) — as quatro do C1 foram removidas junto com o caminho de tenant, e registerTools() só registra as três do C2 hoje:

ToolO que fazMuta?
detect_appDetecta Forma/Instâncias de um repo avulso, com evidências (nunca resolve ambiguidade em silêncio)Não
init_appOnboarding — gera app.yaml e abre PR no repo do app. IdempotenteSim
register_appRegistro — valida o Contrato, confere o render e aplica um AppPlacement no cluster (ADR-0011). Exige namespace, imageRepository, imageTagSim

Nenhuma tool aprova/mergeia PR — decisão deliberada (ADR-0012, decisão 3). Isso é sempre a página web (/pr/{owner}/{repo}/{number}, linkada pela notificação do Slack) ou o GitHub direto; nunca uma tool que um agente possa acionar sozinho.

As skills (.claude/skills/)

SkillPra quemO que faz
onboardar-appDev sem experiência em KubernetesEntrevista em linguagem de negócio, chama só as 3 tools acima — nunca mostra YAML/kubectl/Crossplane
gerar-blueprintQuem já entende Crossplane/schemaCria um Blueprint novo pro catálogo — descreve usar scaffold stack --preview/checkout local. Achado do Grupo H (2026-08-19): core/stack.ts/unstack.ts não existem mais no binário (removidos junto com o caminho de tenant, ADR-0012) — o mecanismo que esta skill descreve está quebrado, não só desatualizado. Não corrigido ainda; avaliar com a plataforma antes de usar.
remover-ambienteQuem já entende Crossplane/schemaDescreve usar scaffold unstack pra tenant inteiro. Mesmo achado: o comando não existe mais. O caminho manual (git/gh/kubectl pra app avulso) continua sendo o único que ainda funciona.

Instalar as skills

As skills vivem versionadas aqui, em tools/scaffolder/.claude/skills/<nome>/SKILL.md, dentro do plat-eng-platform (a fusão da ADR-0012 acabou com plat-eng-scaffolder como checkout separado). Ficam disponíveis automaticamente pro Claude Code quando o cwd está dentro deste checkout (escopo de projeto). Pra ficar disponível em qualquer sessão/repo (ex. abrindo a partir de plat-eng-bootstrap), copiar as três pro escopo global do Claude Code:

# se ainda não tiver um checkout deste repo, cloná-lo primeiro:
git clone git@github.com:quanthubbr/plat-eng-platform.git
cd plat-eng-platform/tools/scaffolder   # se já tinha um checkout, `git pull` antes de copiar

for skill in onboardar-app gerar-blueprint remover-ambiente; do
  mkdir -p ~/.claude/skills/"$skill"
  cp .claude/skills/"$skill"/SKILL.md ~/.claude/skills/"$skill"/SKILL.md
done

Ou uma por vez:

mkdir -p ~/.claude/skills/onboardar-app && cp .claude/skills/onboardar-app/SKILL.md ~/.claude/skills/onboardar-app/SKILL.md
mkdir -p ~/.claude/skills/gerar-blueprint && cp .claude/skills/gerar-blueprint/SKILL.md ~/.claude/skills/gerar-blueprint/SKILL.md
mkdir -p ~/.claude/skills/remover-ambiente && cp .claude/skills/remover-ambiente/SKILL.md ~/.claude/skills/remover-ambiente/SKILL.md

Cópia simples, não symlink — não há sincronização automática nem versionamento da cópia global. Depois que uma das três mudar aqui (git pull trazendo um SKILL.md novo), repetir o cp daquela skill específica pra atualizar a instalação global — nada avisa sozinho que a cópia global ficou desatualizada.

Rodando cada casca localmente

CLI: só SCAFFOLD_GITHUB_TOKEN pros comandos de escrita (bump/init); register/ promote/status pedem credencial de cluster, não de GitHub (ver seção da CLI acima).

Webhook (bun src/app.ts): GITHUB_WEBHOOK_SECRET, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY (PEM, aceita \n literal). Não precisa de SCAFFOLD_GITHUB_TOKEN nem de installation ID fixo — o ID vem do payload de cada evento.

API (bun src/web/server.ts): GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY (mesmos do webhook), GITHUB_APP_INSTALLATION_ID (aqui sim fixo — sem payload de evento pra ler dinamicamente), GITHUB_OAUTH_CLIENT_ID + GITHUB_OAUTH_CLIENT_SECRET (login do usuário, mesmo App, não um OAuth App separado), DASHBOARD_SESSION_SECRET (cifra o cookie de sessão), ARGOCD_API_TOKEN (+ ARGOCD_BASE_URL, default o DNS interno do cluster) + credencial de cluster pras rotas de AppPlacement (mesma da CLI).

Notificação no Slack (core/notify/slack.ts, ADR-0012 Grupo E)

SLACK_BOT_TOKEN (token de bot com escopo chat:write) e SLACK_CHANNEL (ID ou nome do canal) — opcionais, e usados só por init (Onboarding), o único comando que ainda abre PR e tem o que anunciar. register não notifica: desde a ADR-0011 ele não abre mais PR, aplica direto no cluster e devolve o resultado síncrono pra quem chamou. Sem as duas variáveis, init continua abrindo PR normalmente; a notificação é só pulada e logada uma vez, nunca erro fatal (diferente do padrão de credencial obrigatória do OAuth de usuário acima). Ação do usuário, não deste código: criar o app/bot do Slack, o canal de destino, e provisionar as duas variáveis como secret (runbook ADR-0012, Grupo E1) — ainda não existem em nenhum ambiente.

On this page