Skip to content

feat(sdk): cliente tipado gerado do spec servido cobre as 213 operações (core#125) - #139

Merged
fabianocruz merged 4 commits into
mainfrom
feat/125-sdk-gerado-do-spec
Sep 10, 2026
Merged

feat(sdk): cliente tipado gerado do spec servido cobre as 213 operações (core#125)#139
fabianocruz merged 4 commits into
mainfrom
feat/125-sdk-gerado-do-spec

Conversation

@fabianocruz

@fabianocruz fabianocruz commented Sep 9, 2026

Copy link
Copy Markdown
Member

O que muda

Rate card v1 (business-models/codespar-pricing-proposal-2026-08-07.md no shared drive, adotado em 09/09/2026) vira a única verdade de preço no web: docs, /pricing, /product e o dashboard dizem a mesma frase, os mesmos quatro números e a quinta linha (Account governance).

  • Dashboard /dashboard/billing. A grade hobby/starter/growth por cota de tool calls, que levava a um Stripe Checkout REAL, sai. Entra o cartão Pay fee lido de GET /v1/fees/movimentar (codespar-enterprise#906), com três estados honestos: carregando, "Not computed yet" (rota 404/null) e valor com badge "Not yet charged". Abaixo, os quatro números do rate card lidos de src/lib/rate-card.ts. A barra de cota de tool calls ("Upgrade to continue") também sai: tools são R$0 sem limite. createCheckout foi removido de src/lib/actions/billing.ts (export "use server" é endpoint mesmo sem botão); fica só createPortal, atrás de billing.has_subscription.
  • Docs. concepts/billing.mdx, faq.mdx e how-it-works.mdx deixam de vender "US$0,10 por transação liquidada + 0,5% FX, sem assinatura" e passam a dizer: Build, measure and think: free. Move money under mandate: 10 bps. Get paid: 1%. Never more than R$2.00 per transaction. Rails always pass through at the partner's price. Free tier de R$1.000 liquidados/mês por lane por org, estorno do fee em refund de 7 dias, sem mínimo/platform fee/rev-share, BRL. concepts/refunds.mdx, cookbooks/marketplace-payout.mdx e o product.pricingSubtitle (três locales) também citavam US$0,10 e foram alinhados.
  • /pricing (en, pt-BR, es-419). Já falava rate card, mas imprimia preço fechado na lane OPERAR (R$0,10/desfecho, R$0,02 mensagem, 500 grátis) que o documento traz entre colchetes, e dizia que Pay "cobra hoje" quando o documento o coloca pré-GA. Agora OPERAR diz "Included while in preview" sem número; o rodapé diz que só Gate e Embed têm preço vigente e o resto é compromisso travado 24 meses, R$0 até o GA; "10bps"/"R$2" viram "10 bps"/"R$2.00" como nos docs; os três cenários mensais viram os quatro exemplos óbvios abaixo.
  • Account governance (lane GOVERNAR). Quinta linha, decidida em 31/08 e ausente do site: R$4,90 por conta governada ativa no mês, R$0 na conta sem desfecho. Fonte: scripts/v5_model.py (account_fee = 4.90) e positioning paper ED5. Entra em concepts/billing, faq, how-it-works, na /pricing (lane própria + linha do recibo, três locales), no JSON-LD da home e no bloco do rate card do dashboard, com "not yet charged" enquanto o backend não mede contas. Nunca com o nome antigo; o spec reprova Relationship como nome de linha.
  • Tabela de exemplos, igual nas quatro fontes: R$0,30 movido = R$0,05 (piso) · R$100 movido = R$0,10 · R$5.000 movido = R$2,00 (teto) · R$100 recebido = R$1,00 · R$0,50 recebido = R$0,005 (sem piso) · R$900 no mês = R$0 (free tier) · 12 contas governadas = R$58,80. Na /pricing os cartões viram tabela de sete linhas lida das messages.
  • Guarda. tests/unit/rate-card-consistency.spec.ts lê os 3 mdx e as messages da /pricing (3 locales) e reprova se um dos seis valores divergir (10 bps, piso R$0,05, 1%, teto R$2,00, R$4,90/conta, free tier R$1.000), se um dos sete exemplos não bater com moveFeeMinor/getPaidFeeMinor/governanceFeeMinor (recalculado, não comparado a texto; getPaid é exato, meio centavo é meio centavo), se "0.10 per settled"/"per settled transaction"/"subscription"/"cross-border surcharge"/"Relationship" voltar, ou se qualquer valor em BRL colar numa unidade de desfecho (OPERAR).

Rebase sobre main de hoje (4085f6fc, já com o #832): limpo, zero conflito nas duas passagens (fc4b8c02 e 4085f6fc). main não tocou src/app/dashboard/billing/page.tsx desde a base do PR; tocou os três mdx, que este PR reescreve por inteiro. Preservado: tokens v3, modal Enterprise, três estados do cartão de fee.

Evidência

Prova Comando Resultado
Rebase git rebase origin/main limpo, 0 conflitos; 7 commits sobre 4085f6fc (#832)
Zero Stripe Checkout de plano grep -rn 'createCheckout|PLAN_DISPLAY|handleUpgrade' src tests 0 linhas; createPortal só em billing.ts e na página, atrás de has_subscription
Preço único nas 4 fontes npx playwright test --config=playwright.unit.config.ts tests/unit/rate-card-consistency.spec.ts 12 passed (seis valores, sete exemplos, três locales)
Controle positivo A R$2.00R$3.00 em billing.mdx vermelho: "missing /never more than R$2.00 per transaction/"
Controle positivo B ex1Fee R$0.10→R$0.20 em en.json vermelho: "missing example fee R$0.10 for R$100"
Controle positivo C frase "$0.10 per settled transaction, no subscription" no faq.mdx vermelho: "matched /0[.,]10 per settled/"
Controle positivo D "R$0,10 por desfecho" no pt-BR passava na 1ª versão; guarda de OPERAR adicionada (commit 7c7a95ee); agora vermelho
Controle positivo E R$2.00 literal no TSX da página vermelho (página só pode ler messages)
Controle positivo G1 R$4.90R$5.90 no faq.mdx vermelho: "missing R$4.90"
Controle positivo G2 governTitle = "Relationship" no en.json vermelho: "matched /\bRelationship\b/"
Controle positivo G3 ex7Fee R$58,80→R$58,00 no es-419 vermelho
Controle positivo G4 exemplo digitado errado no próprio rate-card.ts (R$0.005→R$0.01) vermelho: recalculado pela função não bate
Reversão dos controles git checkout 12 passed
npm run check tsc + next lint + eslint tests, tree rebased rc=0 (warnings pré-existentes, nenhum nos arquivos tocados)
npm run build sob /tmp/codespar-heavy-lock.sh, tree rebased rc=0 (duas vezes: após a linha de conta e após a troca da rota)
npm run test:unit sob lock, tree rebased rc=0, 447 passed (base do #832 + 12 do spec novo); billing-no-mock + rate-card-consistency rodados de novo após a troca da rota: 17 passed
npm run docs:api:check rc=0 (content/docs/api/reference/ intocado)
npm run check:meta-tool-docs rc=0 (content/docs/concepts/meta-tools/** intocado)
npm run test:scripts rc=0
next start + GET sob lock, porta 3987, tree rebased rc=0 (tree final, após a troca da rota): /pricing 200, /docs/concepts/billing 200, /pt/pricing 200; seis valores, sete exemplos, "Account governance", "Included while in preview" e /v1/fees/movimentar no HTML; nenhuma frase do modelo antigo, nem /v1/billing/movimentar, nem Relationship; servidor morto ao fim
CI (1º push, base fc4b8c02) gh pr checks 786 (poll 60s) tudo verde: checks, build, docs-link-audit, meta-tool-docs, smoke-marketing, smoke-dashboard, audit-guide-acs, Vercel web+docs
CI (push final, base 4085f6fc) gh pr checks 786 (poll 60s, limite 40 min) CI_ROW

CI: os quatro validate-example vermelhos no main (core#135)

Estavam vermelhos no main desde 03/09 (último verde: run 30132831940 em 08d2cc1, 2026-07-24) e caíram igual no primeiro run deste PR. Não é segredo do repo: o job ci não usa secrets. nenhum e o 401 vem do runtime local em localhost:3000. Duas camadas, cada uma com o log antes e depois:

commit antes (log literal) depois
79247dc: validate.sh cria .codespar com modo 0777 antes do docker run (7 scripts; gitignored) runtime did not become healthy in 30s + [server] EACCES: permission denied, mkdir '/example/.codespar' (runtime roda como uid 1000, checkout do runner é uid 1001/755) validate.sh: runtime up after 5s (run 34426921902)
e660b82: validate.sh passa -e ENGINE_API_TOKEN="$DEMO_API_TOKEN" ao container e CODESPAR_API_KEY ao vitest, mesmo padrão do validate-bridge.sh do runtime session create failed: 401 {"code":"api_token_invalid", ... "If you set ENGINE_API_TOKEN, send that value instead."} (o :main exige bearer e cunha token; o :latest de maio ignorava o placeholder) os 4 jobs verdes (run 34427917115: 42s, 45s, 58s, 40s); os 3 em :latest seguem verdes

E um flake meu: 0569cf1 dá 60 s ao teste que regenera 1,1 MB de tipos em processo (250 ms local, Test timed out in 5000ms no runner com o turbo rodando todos os pacotes; verde em 553e30f, vermelho em 79247dc com código idêntico).

Cético

  • R$4,90 tem uma premissa interna que NÃO imprimi. v5_model.py calcula account_fee_eff = min(4.90, 2% do fluxo mensal da conta) com o comentário "R$4,90 é preço de tabela, não preço cobrável em toda banda"; é premissa de modelo, não termo publicado, então as páginas dizem R$4,90 por conta ativa e nada sobre o teto de 2%. Se esse teto for termo comercial, precisa entrar no documento canônico antes de ir pra página.
  • Número fora do documento. Todo número nas páginas vem da seção 2 do documento (0 / 10 bps / 1% / R$2,00; piso R$0,05; free tier R$1.000; refund 7 dias; degraus 8/6 bps e 0,7%/0,5%; FX 0,5%/0,3%). Uma leitura que fiz por conta própria: o documento diz free tier "por lane, por org" e o lead resumiu "por org"; escrevi "per lane, per organization" porque o documento manda. A /pricing continua imprimindo os degraus enterprise e o FX publicado, que estão no documento mas não na frase-mãe.
  • Stripe Checkout ainda alcançável? Não pelo web: createCheckout foi apagado e não existe POST /v1/billing/checkout chamado em src/. O backend ainda pode expor a rota; isso é ent#906/lane preco-enterprise. createPortal continua e é intencional (org com acordo pago vivo).
  • NEXT_PUBLIC_STRIPE_* / sk_test_. NEXT_PUBLIC_STRIPE_PK aparece em src/components/mcp/stripe-demo.tsx (chave publicável do demo Stripe do MCP, pré-existente, não é billing). sk_test_ só como placeholder de docs (install/page.tsx, server-details.json) e sentinela em tests/unit/*.setup.ts. Nenhum segredo no diff. Memória do projeto: houve sk_test_ em produção no self-serve; não é este repo, mas fica registrado.
  • Cor literal em componente. Zero nova: o badge "Not yet charged" trocou rgba(245,158,11,0.1) por V3.surface+V3.line. A /pricing já carregava 23 hex literais (accents de produto espelhando /product) antes deste PR; não mexi.
  • rc atrás de pipe. Todos os rc na tabela são do comando: lock cmd > log 2>&1; echo rc=$?. O único pipe (playwright | grep) é só para resumir a saída; o resultado veio do texto "12 passed"/"1 failed".
  • next start sem Clerk responde 500. A primeira sondagem deu rc=23 porque o middleware exige NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; repetida com as mesmas sentinelas pk_test_/sk_test_unit_sentinel… que tests/unit/middleware/setup.ts usa (nunca uma chave real). O 200 nas páginas públicas não depende de Clerk, só de o middleware carregar.
  • gh pr checks --watch com timeout. A primeira tentativa morreu com rc=127 (timeout não existe no macOS) e o log mostrava tudo pendente; substituído por loop de 60 s com prazo. Está na linha CI acima.
  • dev:up (portão pré-push do CLAUDE.md). Não rodei: sem codespar-enterprise neste clone. O smoke de dashboard roda no CI contra o preview Vercel; a linha CI cobre.
  • Perdi meu próprio trabalho uma vez. O controle positivo fez git checkout em arquivos ainda não commitados e reverteu billing.mdx, how-it-works.mdx e en.json; refiz e commitei antes de repetir os controles. O diff final foi verificado depois disso (git diff origin/main..HEAD).

Fica de fora

  • Lane OPERAR sem preço. Os valores R$[0,10]/[0,05]/[0,02] e free tier [500] continuam entre colchetes no documento; as páginas dizem "included while in preview" e a guarda reprova qualquer número ali até o Fabiano fechar.
  • Backend 404 até ent#906 entrar. GET /v1/fees/movimentar ainda não existe em produção; o cartão mostra "Not computed yet". Shape camelCase (consumerCount, movedMinor, feeMinor, rateTier) conferido no diff do #906. A rota saiu de /v1/billing/… porque a matriz v2.1.1 reserva /billing ao BFF do dashboard e o smoke do dev-up do enterprise reprova a substring; string trocada em billing.ts, billing.mdx e tests/dashboard/billing.spec.ts (grep repo inteiro: zero referência velha).
  • Escopo fees:read. Não muda nada para o usuário do dashboard: o web chama o backend com x-codespar-service-key (service auth, src/lib/backend.ts), não com chave csk_ escopada. O escopo só importa a quem chamar /v1/fees/movimentar com API key própria, e isso não é a superfície documentada.
  • Get paid (FATURAR) e a linha de conta não são cobradas pelo backend. Só MOVIMENTAR está no ent#906. As páginas dizem isso com todas as letras (callout do billing.mdx, footnote e lane da /pricing, bloco do dashboard).
  • GET /v1/fees com computed/charging por lane. O ent#906 passou a expor também GET /v1/fees dizendo, por lane (MOVIMENTAR, FATURAR, GOVERNAR, OPERAR), o que está computado e o que está sendo cobrado. Este PR não lê isso: o cartão de fee e as marcas "not yet charged" das páginas são texto fixo. Follow-up: o dashboard e as páginas passam a derivar a marca desses dois campos em vez de texto; a ligação mora em ent#1189 (lanes sem cobrança).
  • GET /v1/billing continua sendo lido só para has_subscription; o doc de billing não documenta mais o shape antigo (unit_price_usd: 0.10) porque ele era ficção.
  • /mcp "PAID · Provider Pricing" (Fiscal: R$0.10-0.50 per NFe, Messaging: R$0.01-0.10 per message) descreve custo de provider externo, não linha CodeSpar; deixei, mas é candidato a revisão junto com OPERAR.
  • content/docs/meta.json não precisou de mudança.
  • Sem merge: é billing, o Fabiano mergeia.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FV48HsCVN4Q53Yt3GwrDGG

fabianocruz and others added 4 commits September 9, 2026 20:41
…es (core#125)

`cs.api` é um cliente REST tipado por caminho e método, gerado do
documento OpenAPI servido em https://api.codespar.dev/openapi.json
(173 caminhos, 213 operações). Nenhum método escrito à mão: o snapshot
do documento é commitado com sha256, data e origem; os tipos saem do
openapi-typescript; a tabela de operações (método, caminho, content
types) sai do mesmo snapshot e é amarrada ao tipo `paths` por
`satisfies`.

Portões:
- vitest: sha do snapshot confere; src/generated/ é igual ao que o
  snapshot gera; a tabela tem as 213 operações do snapshot; cada uma é
  despachada pelo cliente com método, URL e content types corretos;
  controle positivo (remover uma operação do snapshot dá diff).
- `npm run sdk:spec:check`: reprova snapshot editado à mão, gerado
  desatualizado, e snapshot divergente do documento servido (rc 1;
  rc 2 quando não consegue buscar). `npm run sdk:spec:refresh`
  rebaixa e regenera. Roda no workflow hosted-runtime-smoke.

Versão: @codespar/sdk 0.12.0 (minor, superfície nova). O bump é a
decisão de release que o waiver core#131 esperava; waiver removido.
cli 0.6.1 (range ^0.12.0) e adapters patch (peer range || ^0.12.0),
mesmo padrão de 0c0aacd, para o workspace não aninhar o sdk.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FV48HsCVN4Q53Yt3GwrDGG
…re#135)

Os quatro jobs validate-example pinados em ghcr.io/codespar/codespar:main
estão vermelhos no main desde 03/09 (core#135), com a mesma assinatura no
run do main em 15dec02 e no run deste PR: o runtime roda como `node`
(uid 1000), o checkout no runner pertence ao uid 1001 com modo 755, e o
`mkdir /example/.codespar` morre em EACCES antes do /health responder.
CODESPAR_STATE_DIR não redireciona (core#135, item 3).

Conserto: o validate.sh cria `$SKELETON_DIR/.codespar` com modo 0777
antes do `docker run`, nos sete scripts que fazem o mesmo bind mount
(os três em :latest rodam como root e não precisam hoje; ganham a
mesma linha pelo motivo do #137). O diretório já é gitignored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FV48HsCVN4Q53Yt3GwrDGG
O teste "src/generated/ equals what the snapshot generates" regenera
1,1 MB de tipos em processo: ~250 ms num laptop, mas passou dos 5 s
padrão do vitest no runner do CI com o turbo rodando a suíte de todos
os pacotes ao mesmo tempo (verde em 553e30f, vermelho em 79247dc com o
código idêntico). Timeout explícito de 60 s; o teste não mudou.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FV48HsCVN4Q53Yt3GwrDGG
…#135)

Com o estado gravável (79247dc) o runtime :main sobe (`runtime up after
5s` no CI), e o demo cai na camada seguinte: `session create failed:
401 api_token_invalid`. O runtime atual exige bearer em toda rota e,
sem ENGINE_API_TOKEN, cunha um token no primeiro boot e grava em
.codespar/api-token; o teste manda o placeholder "demo"/"local" que o
:latest (maio) ignorava.

Conserto, o mesmo que o scripts/validate-bridge.sh do próprio runtime
faz: o validate.sh define DEMO_API_TOKEN (CODESPAR_API_KEY ou o
default do teste), passa como ENGINE_API_TOKEN ao container (nada é
cunhado nem gravado) e como CODESPAR_API_KEY ao vitest do modo docker.
Sete scripts, mesmo bloco.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FV48HsCVN4Q53Yt3GwrDGG
@fabianocruz
fabianocruz merged commit e897de4 into main Sep 10, 2026
11 checks passed
@fabianocruz
fabianocruz deleted the feat/125-sdk-gerado-do-spec branch September 10, 2026 02:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant