Prophub Cloud Foundation

Arquitetura — plat-eng-scaffolder

O motor core/, as 3 cascas (CLI, GitHub App, API), o contrato app.yaml e o fluxo do webhook.

core/ — o motor compartilhado

Nenhuma das 3 cascas tem lógica própria de negócio — todas chamam as mesmas funções em core/:

MóduloResponsabilidade
detect/Heurística que infere shape/instances/porta/health a partir de Dockerfile + package.json + grep no código
contract/Motor de validação único (Ajv2020) — schemas de app.yaml, app-delivery.yaml, values.yaml por shape, blueprint.yaml
generate/Gera app.yaml, Dockerfile placeholder, workflow de CI (build-and-bump.yml)
github/Abstração de escrita única (GitHubClient) + auth de instalação do App (JWT) + auth OAuth de usuário (ADR-0011) + client via API (sem checkout local)
argocd/Client REST read-only do ArgoCD (listApplications, getApplication)
init.ts / register.ts / bump.ts / stack.ts / unstack.ts / restack.ts / validate.tsUm "engine" por comando — a mesma função que a CLI chama é a que o webhook e a API chamam

restack.ts (edita resources/scaling de um app já instanciado, preservando comentários do values.yaml) só é exposto pela API — não existe comando scaffold restack na CLI.

As 3 cascas

CascaEntradaDeployAutenticação
CLI (cli.ts)bun run src/cli.ts <comando>não deployada, roda localSCAFFOLD_GITHUB_TOKEN (PAT)
GitHub App / webhook (app.ts)POST /webhookscaffolder, Dockerfile raizinstalação do App (JWT + installation token)
API web (src/web/server.ts)/api/* + /docs (Scalar) + /mcp (MCP server, ADR-0012)scaffolder-api, Dockerfile.websessão OAuth do usuário (aprovar/mergear) + instalação do App (abrir PR) + OAuth do usuário via MCP (Onboarding/Registro/Blueprint dev-facing)

Sem framework em nenhuma das duas cascas HTTP — Bun.serve() puro, decisão deliberada (ADR-0009). Sem frontend desde 2026-08-10 — a API é o produto, quem consome decide a UI. O MCP (/mcp, src/web/mcp/) não é uma quarta casca — mesmo Bun.serve(), mesmo deployable da API web, só um transporte a mais na frente do mesmo core/; detalhe das 7 tools e das skills que as consomem no Manual.

O contrato app.yaml

apiVersion: prophub.tech/v1, kind: App, additionalProperties: false em tudo (campo desconhecido reprova, não ignora). Campos:

  • metadata.name (obrigatório, DNS-1123) / metadata.stack (opcional, rótulo de agrupamento)
  • spec.shape (obrigatório) — static-web | web | worker | job
  • spec.instances (obrigatório) — single | per-tenant
  • spec.build.dockerfile (default Dockerfile) / spec.build.buildArgs (só nomes, nunca valores)
  • spec.runtime.port / spec.runtime.healthPath (default /) — obrigatório se shape é static-web/web
  • spec.ingress.host (literal, só quando instances: single) ou spec.ingress.hostPrefix (só quando instances: per-tenant) — nunca os dois juntos, o schema proíbe
  • spec.env.fromSecret (default false)
  • spec.resources.cpu/.memory, spec.scaling.{min,max,targetCPU} — proibidos quando shape é worker/job
  • spec.worker.consumerName — só quando shape: worker, nunca pode conter {{ }} sobrando
  • spec.job.schedule — só quando shape: job; presente vira CronJob, ausente vira Job único

O webhook — 4 eventos, não 3

A RFC-0006 original documenta 3 eventos; o código hoje implementa 4:

  1. pull_request (opened/synchronize) — busca app.yaml no HEAD do PR, valida, publica um Check Run scaffold validate (sucesso/falha) com o motivo.
  2. push — só loga; a escrita de versão real acontece dentro do workflow de CI que o scaffold init gerou (que chama scaffold bump), não aqui.
  3. repository (created) — checa se o repo novo já nasceu com app.yaml; se sim e ainda não está registrado em nenhum app-delivery.yaml, comenta sugestão de scaffold register no PR que introduziu o arquivo (ou abre issue se não achar PR).
  4. issue_comment (/scaffold init, primeira linha exata) — checa permissão admin/write do autor do comentário, e se autorizado chama o mesmo initFromRepo() que a CLI chama, lendo o repo via API do GitHub em vez de checkout local.

Todos os 4 autenticam como a instalação do App (nunca PAT) — o installationId vem do próprio payload do evento.

On this page