ADR 0056 — Customer Auto-Provisioning Workflow (Temporal)¶
- Status: Accepted
- Date: 2026-05-14
- Deciders: André Luiz Gallon (Architect/Operator)
- Supersedes: —
- Related: ADR 0053 (Cell-based Hyperscale), ADR 0054 (PQC-Everywhere), ADR 0055 (Admin Console + Marketplace)
- Memory:
project_octopus_hyperscale_admin_pqc_locked_2026_05_14.md
Context¶
Para hyperscale rumo a 100 000+ clientes, NÃO podemos depender de operador humano no caminho do onboarding. Cada signup precisa fluir zero-friction, zero-touch, com SLO < 30 segundos do "cliente paga" ao "cliente tem tokens habilitados no Dashboard".
Pontos críticos:
- Idempotência — Stripe webhook pode reentregar 5× em casos de timeout; nunca provisionar 2 deployments para 1 pagamento
- Durabilidade — se o orchestrator cair no meio do workflow, retomar exatamente de onde parou, não do início
- Visibility — operador admin precisa inspecionar todo workflow em execução (status, step atual, retry count, falhas, tempo gasto)
- Compliance — toda decisão automatizada vai para audit chain Merkle (regulamento GDPR/LGPD: cliente tem direito a saber quem/o que decidiu seu provisioning)
- Retriabilidade — falha em step N não invalida steps 1..N-1; retry só do step que falhou
- Saga compensation — se step N falha após M steps já completados, executar compensating actions para rollback (revogar cert, deletar tenant, refund Stripe)
Workflows imperativos com state machine ad-hoc + cron retry = receita para drift. Temporal.io (originalmente Cadence/Uber) resolve isto com workflows duráveis baseados em event sourcing + deterministic replay.
Decisão¶
OCTOPUS adota Temporal.io como motor de workflow durável para auto-provisioning e para outros workflows administrativos (renovação cert, churn flow, suspensão por pagamento falho, etc.).
Stack workflow¶
| Componente | Tecnologia |
|---|---|
| Workflow engine | Temporal.io Cloud (Plano Pro) OU self-hosted Temporal Cluster |
| SDK | Temporal Go SDK (consistência com resto da stack) |
| Activities | Go funcs idempotentes, retry-policy por activity |
| Persistence | Cassandra (Temporal default) OR Postgres + Elasticsearch para search |
| UI | Temporal Web UI (embedded em admin.tlsstress.art via iframe RBAC-gated) |
Workflow canônico: customer.OnboardingV1¶
┌──────────────────────────────────────────────────┐
│ Customer clica "Buy" em tlsstress.art │
└─────────────────────┬────────────────────────────┘
│
┌─────────────────────▼────────────────────────────┐
│ Stripe Checkout (cartão / wire / Apple Pay) │
└─────────────────────┬────────────────────────────┘
│
┌─────────────────────▼────────────────────────────┐
│ Stripe webhook → Provisioning Orchestrator │
│ (verifica assinatura HMAC; idempotency_key) │
└─────────────────────┬────────────────────────────┘
│
┌─────────────────────▼────────────────────────────┐
│ Temporal: StartWorkflowExecution │
│ WorkflowID = "onboarding-<stripe_session_id>" │
│ (idempotência built-in via WorkflowID) │
└─────────────────────┬────────────────────────────┘
│
┌─────────────────────────────────────▼─────────────────────────────────────┐
│ Activity 1: KYC (Sift / Stripe Radar fraud score) — opcional │
│ Activity 2: Mint DeploymentID (ULID, collision-checked) │
│ Activity 3: Allocate cell (GeoIP customer billing address → nearest) │
│ Activity 4: Issue PQC client cert via Vault PKI (per-cell CA) │
│ Activity 5: Provision tenant schema (Postgres RLS) + Redis namespace │
│ Activity 6: Allocate token quota (UTXO ledger, initial mint) │
│ Activity 7: Configure CONNECT.Art slot reservation │
│ Activity 8: Configure STUN-coord slot reservation │
│ Activity 9: Generate onboarding link (signed JWT, 24h TTL) │
│ Activity 10: Send welcome email (Postmark, transactional) │
│ Activity 11: Audit chain append (Merkle entry per step) │
│ Activity 12: Notify admin queue (optional: high-value customer flag) │
└─────────────────────┬─────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Customer clica link → Dashboard loads │
│ Tokens já ativos. SLO < 30s end-to-end. │
└──────────────────────────────────────────────────┘
Idempotência¶
- Stripe: webhook usa
idempotency_key = stripe_session_id. TemporalWorkflowIDé derivado deste mesmo ID → reentregas de webhook NÃO disparam workflows novos - Activities: cada activity recebe
idempotency_keyderivado de(WorkflowID, ActivityName, Attempt). Vault PKI rejeita issue duplicado para mesma chave; Postgres tenant create é UPSERT - Email: Postmark
MessageStream+MessageReferenceprevine duplicate send
Saga compensation¶
Se Activity N falha permanentemente após retries esgotados:
Activity 1-N-1 (já completadas) → reverse order compensating:
- Activity 12 (notify admin): no-op (notification não compensável; loga)
- Activity 11 (audit append): append "rollback" entry (audit chain é append-only)
- Activity 10 (email): no-op (email enviado é enviado)
- Activity 9 (onboarding link): revoke JWT em revocation list
- Activity 8 (STUN slot): release reservation
- Activity 7 (CONNECT slot): release reservation
- Activity 6 (token quota): burn allocated tokens (UTXO transition)
- Activity 5 (tenant schema): mark "rollback_pending" (sweep nightly drops)
- Activity 4 (PKI cert): revoke cert (CRL update + OCSP responder)
- Activity 3 (cell allocation): release allocation
- Activity 2 (DeploymentID): mark "rolled_back" in ID registry
- Activity 1 (KYC): no-op (KYC score persisted; reuso futuro)
Final action:
- Stripe: refund full amount (Activity 0)
- Send "provisioning failed, refund issued" email
- Page on-call admin via PagerDuty
SLO¶
| Métrica | Target | Alerta |
|---|---|---|
| Workflow p50 latency (start → email sent) | < 15s | > 30s |
| Workflow p99 latency | < 30s | > 60s |
| Workflow success rate | > 99.5% | < 99.0% sustained 1h |
| Saga compensation rate | < 0.2% | > 0.5% sustained 1h |
| Email delivery success | > 99.5% | < 99.0% sustained 1h |
Stripe webhook hardening¶
- Endpoint:
https://provisioning.tlsstress.art/v1/stripe-webhook(separado de admin.tlsstress.art para isolar blast radius de webhook abuse) - Assinatura HMAC-SHA256 verificada com Stripe webhook secret rotacionado trimestralmente
- Rate limit: 100 req/s por IP, burst 200 (Stripe nunca excede isto)
- Replay protection:
Stripe-Signatureheader com timestamp; rejeita > 5min skew - Idempotency: dedup window de 7 dias via Redis + persisted in Postgres webhook_events
- IP allowlist: somente Stripe IPs publicados (validados via Stripe API hourly)
- Payload PQC-encrypt re-encryption antes de enfileirar em Temporal (Stripe ainda não PQC; mas após validação HMAC e antes de persistir, criptografamos novamente com AES-256-GCM PQC-safe + ML-KEM-768 envelope)
Outros workflows na mesma plataforma Temporal¶
customer.RenewalV1— renovação cert anual (cert-manager + Vault)customer.ChurnV1— cliente cancela (saga reversa de Onboarding)customer.SuspendV1— pagamento falho 3× (suspende sem deletar dados; restore se pago)customer.QuotaTopupV1— cliente compra pacote adicional de tokenscustomer.UpgradeV1— cliente muda de tier (Pro → Enterprise)system.CellRebalanceV1— operations rebalance cells quando saturação > 70%system.CertRotationV1— rotação cert global (Sigstore + Vault)system.AuditChainReconcileV1— daily Merkle root reconcile
Alternativas consideradas¶
| Alternativa | Por que rejeitada |
|---|---|
| Cron + state machine ad-hoc em Postgres | Receita para drift. Visibility ruim. Idempotência manual = fonte constante de bugs. |
| AWS Step Functions | Lock-in AWS. Limitações de payload size + state. Não multi-cloud-portable. |
| Cadence (Temporal predecessor) | Temporal é o fork maintido. Cadence Uber stale. |
| Apache Airflow | Designed for batch / ETL, não para workflows interativos sub-30s. |
| Custom orchestrator em Go | Reimplementar Temporal = 6-12 meses. Não vale. |
Consequências¶
Positivas¶
- Onboarding 100% automatizado, zero humano no caminho até Enterprise tier
- Visibility completa via Temporal UI (admin pode inspecionar workflow em tempo real)
- Saga compensation built-in resolve cleanup de falhas parciais
- Reuso da plataforma Temporal para todos workflows admin futuros (renewal, churn, etc.)
- Hyperscale-ready: Temporal handles 100k+ workflows/s sem problemas
Negativas¶
- Custo Temporal Cloud ~$300-1500/mês inicial (depende volume); self-hosted Cluster custa Cassandra + sysadmin
- Curva de aprendizado: Temporal SDK tem semantics deterministic-replay que pegam dev desavisado
- Workflow versioning é necessário para evolução (Temporal tem suporte built-in mas requer disciplina)
Neutras¶
- Persistence backend (Cassandra default) é hot path — precisa ser HA própria
Wave-1 implementação¶
PR antecipado:
- PR-OCTOPUS-6: Provisioning Orchestrator com
customer.OnboardingV1workflow, Stripe webhook receiver, 12 activities cabladas, saga compensation completa, Temporal Web UI embedded.
Estimativa: 5-7 dias com 1 dev fulltime fluente em Temporal Go SDK.