fix: superfície pós-lane wallet (snapshot OpenAPI com overspent, prosa de codespar_wallet, CLI marca overspent) - #141
Merged
Conversation
…ger opcional, fees) `node scripts/openapi-spec.mjs refresh` em packages/core contra https://api.codespar.dev/openapi.json (0.3.0, 175 caminhos, 215 operações). O que mudou no documento: - `GET /v1/consumers/{id}/wallet`: `available_minor` deixa de ser "floored at 0" (as duas ocorrências do gerado somem) e ganha `overspent: boolean` (ent#1204 / ent#1208). - `POST /v1/wallets/{id}/ledger`: `mandate_id`, `attempt_id`, `external_ref` opcionais (ent#1194). - `/v1/sessions/{id}/execute|proxy_execute|send`: texto do 403. - `/v1/fees`, `/v1/fees/movimentar`: caminhos novos; a tabela de operações e o pino do teste (`api-client.test.ts`) passam de 213 a 215, e README/CHANGELOG 0.12.0 (não publicado; npm serve 0.11.0) seguem. Refs codespar/codespar-enterprise#1208, codespar/codespar-enterprise#1204, codespar/codespar-enterprise#933 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…verspent (ent#1208) - `packages/types/src/meta-tool-definitions.ts` WALLET_DEFINITION: a `description` passa a ser o texto servido em api.codespar.dev/meta-tools.json copiado verbatim (balance devolve `balances[]` canônico; o saldo do provedor viaja como `provider_balance_minor`, só reconciliação). Só prosa; o schema de entrada não muda. Parte do drift ent#933 / ent#1199. - `packages/cli/src/commands/wallet.ts`: `WalletCurrency` ganha `overspent: boolean` (campo do openapi servido) e a coluna `available` imprime `-2500 (overspent)` quando a API o devolve, para o sinal negativo não ser o único aviso; a nota de rodapé explica a marca. Refs codespar/codespar-enterprise#1208, codespar/codespar-enterprise#1204, codespar/codespar-enterprise#933 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…6 é imutável) O portão `check-publish-drift` reprovou: a `description` de WALLET_DEFINITION muda o conteúdo empacotado e 0.10.16 já está no registry. Mesmo rito de #133: package.json, package-lock.json e o `version` de DEMO_SCENARIO_MANIFEST (o teste de lockstep exige igualdade). Sem entrada de CHANGELOG, como em #133. Uma tag de release publica todos os pacotes não-private juntos: a próxima tag leva types 0.10.17 (com sdk 0.12.0, cli 0.6.1 e os adapters 0.4.2, que já estavam com bump pendente). Refs codespar/codespar-enterprise#1208 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fabianocruz
added a commit
that referenced
this pull request
Sep 11, 2026
…s da superfície publicada (core#125) (#142) Fecha a metade CLI da core#125. O SDK 0.12.0 já publicou o cliente tipado sobre as operações do documento servido; a CLI continuava sem os comandos que a matriz v2.1.1 pede e sem superfície nenhuma para triggers. ## O que muda ### Censo antes / depois O documento servido (`openapi-snapshot.json`, API 0.3.0, buscado em 2026-09-10T10:32Z) declara **215 operações** em **48 famílias de rota** — não 213: a lane wallet subiu de 213 para 215 em #141, depois do #139. Agrupando pelo primeiro segmento depois de `/v1/`: | Família (ops) | CLI antes | CLI depois | |---|---|---| | consumers (22) | `wallet <consumer>`, `transfer`, `spend` (rotas à mão, fora do spec) | **`consumers` (18 subcomandos) + `boletos` (4)** | | wallets (15) | nada | **`wallets` (15)** | | triggers (10) | nada | **`triggers` (10)** | | mcp-servers (8) | nada | **`mcp-servers` (8)** | | sellers (6) | nada | **`sellers` (6)** | | admin (0) | nada | nada — **não existe rota servida** | | servers, sessions, connections, connect, whoami, tool-calls, meta-tools (7 famílias) | comandos legados | inalterados, registrados como exceção | | outras 34 famílias | nada | exceção com razão + data | | **15 meta-tools** (`@codespar/types`) | 6 com comando próprio (`discover`, `charge`, `ship`, `ledger`, `issue`, `wizard`) | **as 15 invocáveis** por `codespar tool <nome>`, mais `pay` e `kyc` | Total derivado: **61 subcomandos novos** em 6 grupos, mais `tool`, `pay`, `kyc` e `tools meta`. ### Como os comandos são derivados Nenhum comando novo é escrito à mão. `src/surface.ts` lê `API_OPERATIONS` do `@codespar/sdk` e, para cada grupo publicado (uma linha: nome + prefixo de caminho + descrição), calcula os subcomandos: - os parâmetros do caminho viram posicionais, na ordem do caminho; - `-q, --query key=value` é repetível; `-i/--input` só existe onde a operação declara corpo; - o nome é função pura do conjunto de operações — o sufixo literal abaixo do prefixo quando ele identifica uma operação só, prefixado pelo verbo quando duas compartilham o sufixo (`triggers list-deliveries` vs `triggers get-deliveries`); - o despacho é `cs.api.request(method, path, ...)`. A CLI não acrescenta HTTP nenhum. `codespar tool <nome>` lê `SHARED_META_TOOL_DEFINITIONS` do `@codespar/types` (via reexport do SDK): a lista das 15, o vocabulário de `--action`, o tipo de cada `--arg key=value` e o conjunto obrigatório saem do contrato publicado. Nada é validado contra lista escrita aqui — uma action nova na definição passa a ser aceita sem mudar código. ### Os dois portões 1. **Cobertura da superfície** (`__tests__/surface-coverage.test.ts`): toda família de rotas precisa de comando derivado ou de entrada em `SURFACE_EXCEPTIONS` com razão (mín. 20 caracteres) e data ISO. São 43 exceções hoje, fixadas em `EXCEPTION_PIN`; a catraca só desce, e o texto de falha proíbe explicitamente o conserto preguiçoso (adicionar exceção) e nomeia o conserto certo (uma linha em `PUBLISHED_GROUPS`). O checker `auditSurface()` é função pura da superfície recebida, então roda também contra superfícies sintéticas. 2. **Caminhos escritos à mão**: um scanner lê o próprio `src/` e compara cada literal `/v1/...` com a tabela de operações. Dez caminhos dos comandos antigos **não existem no documento servido** — a lista está fixada e só pode encolher. Achado, não conserto: ver "Fica de fora". `--json` continua imprimindo o payload puro. Sem `--json`, um envelope de coleção vira tabela sobre os campos escalares e o resto sai como JSON. Erros do cliente gerado (`CodesparApiError`, `TimeoutError`) agora imprimem a mensagem e o corpo da API e saem 1, em vez de cair no stack trace de "internal error". ## Evidência Todos os `rc` são do próprio comando (redirecionamento para arquivo, sem pipe). ``` npx turbo run build typecheck test --filter=@codespar/cli... --force FINAL rc=0 @codespar/types:test: Test Files 7 passed | Tests 150 passed @codespar/sdk:test: Test Files 19 passed | Tests 190 passed @codespar/cli:test: Test Files 8 passed | Tests 86 passed ``` **Controles do portão, sintéticos** (6 casos, todos verdes): superfície coberta + exceção escrita passa limpa; grupo sem comando e sem exceção, exceção sem razão, exceção sem data ISO, exceção para grupo que já tem comando, exceção para grupo que sumiu do spec, e meta-tool publicada que a CLI não invoca — cada um produz exatamente a violação esperada. **Controle do portão, ao vivo** (apaguei a exceção `fees` de `SURFACE_EXCEPTIONS` e rodei o portão de verdade): ``` MUTANT rc=1 + "uncovered-group: resource group \"fees\" has no command and no written exception" AssertionError: SURFACE_EXCEPTIONS has 42 entries and EXCEPTION_PIN says 43. RESTORED rc=0 (15 passed) ``` **Controle do scanner de caminhos**: comentário mencionando `/v1/ghost/line` e bloco mencionando `/v1/ghost/block` são removidos antes da varredura; `client.get("/v1/real/path")` e uma URL absoluta com `https://` sobrevivem. Sem isso o scanner contava prosa como tráfego — foi exatamente o que aconteceu na primeira rodada, com os próprios JSDoc de `surface.ts`. **Execução real contra o spec servido** (`api.codespar.dev`, sem segredo — chave inválida de propósito, só para provar que a requisição sai com o método e o caminho certos e que o erro da API aparece verbatim): ``` $ codespar sellers get slr_0000 --api-key csk_test_invalidkey rc=1 ✗ GET /v1/sellers/{sellerId} failed: 401 — unauthorized { "error": "unauthorized" } $ codespar wallets list --api-key csk_test_invalidkey rc=1 ✗ GET /v1/wallets failed: 401 — unauthorized { "error": "unauthorized" } ``` **Validação antes da rede** (nada é enviado; `rc=1` em todos): ``` $ codespar pay ✗ codespar_pay requires action. Pass --action <pay|status> or --input '<json>'. Nothing was sent. $ codespar tool codespar_pay --action nope ✗ codespar_pay --action "nope" is outside the published vocabulary: pay | status $ codespar tool codespar_kyc --action status ✗ codespar_kyc publishes no "action" property, so --action means nothing to it. Its required input is: buyer, check_type — pass it with --arg buyer=<value> or --input. $ codespar tool codespar_wallet --action balance --arg bogus=1 ✗ codespar_wallet has no property "bogus". Published properties: action, consumer_id, ... ``` **Tarball** (`npm pack --dry-run`, rc=0): `@codespar/cli@0.7.0`, 91 arquivos, 72.7 kB / 300.8 kB descompactado. Entram `dist/surface.js`, `dist/commands/meta-tool.js`, `dist/commands/resource.js`. Nenhum arquivo de teste entra — o `tsconfig` já os exclui do build. **Versão proposta: 0.6.1 → 0.7.0** (minor: só adiciona superfície). Bumpada no `package.json` e no CHANGELOG. **Não publicada.** ## Cético - **Comando escrito à mão onde dava para derivar?** Os 61 subcomandos saem de um laço só. O que é escrito à mão: as 6 linhas de `PUBLISHED_GROUPS` (nome + prefixo + descrição) e os dois atalhos `pay` e `kyc`, que a issue nomeia e que delegam ao mesmo runner de `codespar tool`. `boletos` é a única decisão de recorte: um prefixo mais longo (`/v1/consumers/{}/dda`) que rouba 4 rotas de `consumers`, porque a matriz pede o grupo com esse nome. - **Lista de meta-tools hardcoded?** Não existe lista na CLI. Nome, actions, propriedades e obrigatórios vêm de `SHARED_META_TOOL_DEFINITIONS`. O teste percorre as 15 lendo a mesma fonte, então nenhum vocabulário está redigitado no teste — se um rail novo entrar na definição, o teste acompanha em vez de reprovar. - **Saída que finge chamada?** Nenhuma. Todo comando imprime o payload que a API devolveu; `--json` imprime esse payload cru. Erro da API sobe como erro da API. Os exemplos do README e das mensagens usam ids óbvios (`wal_0000`, `slr_0000`, `con_0000`). - **Exceção usada para passar?** As 43 são o estado real do censo: 7 cobertas por comando legado (nomeadas como tal), 5 de handshake / redirect de browser / probe, e o resto famílias fora da onda 4 da matriz. A catraca reprova se o número subir, e o texto de falha diz que levantar o pin exige assinatura sua no corpo do PR. - **rc atrás de pipe?** Nenhum. Toda medição acima é `cmd > arquivo 2>&1; echo rc=$?`. Na primeira tentativa eu tinha lido o `rc` do `head` e obtive `rc=0` para comandos que falhavam; refiz. - **O número 213.** A tarefa dizia 213 operações. O snapshot atual diz 215, e o `git log` mostra a subida em #141. Usei 215. - **Um cast.** `cs.api` é tipado correlacionando caminho literal com método literal; uma CLI despacha um par que é dado em tempo de execução. Há um cast único, comentado, em `commands/resource.ts`. O que ele esconde continua checado dentro do cliente: par desconhecido e parâmetro de caminho faltando lançam antes de qualquer rede. - **Bug que o próprio trabalho pegou.** A primeira versão da derivação perdia os parâmetros que ficam *dentro* do prefixo: `boletos list` saía sem `<consumerId>`, um comando que não consegue nomear consumidor nenhum. Corrigido e pinado em teste. ## Fica de fora - **`admin`.** A matriz v2.1.1 nomeia a família admin/account, mas o documento servido não declara **nenhuma** rota `/v1/admin/*`. Não há o que derivar. Em vez de exceção, o portão afirma o fato: um teste exige que `/v1/admin` continue vazio e fica vermelho no dia em que as rotas subirem. - **Os dez caminhos fora do spec.** Comandos anteriores montam à mão `/v1/tools`, `/v1/tools/{}`, `/v1/logs/stream`, `/v1/consents/init`, `/v1/consents/{}/submit`, `/v1/consumers/mandates/{}/spend`, `/v1/consumers/{}/wallet/transfer`, `/v1/sessions/{}/logs`, `/v1/sessions/{}/close` e `/v1/servers/{}` — nenhum declarado no documento servido. Não migrei: ou o backend está com a OpenAPI incompleta, ou os caminhos estão errados e dariam 404, e eu não consigo distinguir os dois sem rodar contra um ambiente autenticado. Registrados na catraca para que o número não cresça enquanto isso é decidido. **Vale issue própria.** - **Execução autenticada de verdade.** Não havia chave de teste disponível sem segredo nesta máquina, então nenhum comando rodou com resposta 2xx real. O que rodou: as duas chamadas acima contra `api.codespar.dev` (401 da própria API, provando método, caminho e tratamento de erro) e os testes de despacho com `fetch` mockado, que afirmam URL expandida, método, corpo, `Authorization` e `x-codespar-project`. - **Paridade Python.** A matriz anota paridade para parte destas linhas; este PR é só TypeScript. - **Grupos fora da onda 4** (orgs, projects, policies, commerce-memory, payment-links, paywalls, audit-events, agents, ofb e companhia): a máquina já existe, cada um é uma linha em `PUBLISHED_GROUPS` quando for pedido. - **Suíte inteira do workspace e docker**: não rodei, por pressão de memória na máquina. Rodei `build`, `typecheck` e `test` para `@codespar/cli...` (que arrasta `@codespar/types` e `@codespar/sdk`). - **Publicação**: não publicada. A versão está bumpada como proposta. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Metade
codespar-corede codespar/codespar-enterprise#1208 (lane wallet, PR codespar/codespar-enterprise#1204; drift de prosa é o de codespar/codespar-enterprise#933).O que muda
node scripts/openapi-spec.mjs refreshempackages/core, que é o mecanismo do repo: servido →openapi-snapshot.json→src/generated/openapi.ts+operations.ts), commit próprio. As duas ocorrências de "floored at 0" no gerado somem eoverspent: booleanaparece emGET /v1/consumers/{id}/wallet. O diff por caminho: esse,POST /v1/wallets/{id}/ledger(mandate_id/attempt_id/external_refopcionais, ent#1194),/v1/sessions/{id}/execute|proxy_execute|send(texto do 403) e dois caminhos NOVOS,/v1/feese/v1/fees/movimentar. A tabela de operações vai de 213 a 215, então o pino emapi-client.test.tssobe a 215 de propósito, e README/CHANGELOG de 0.12.0 (não publicado; npm serve 0.11.0) dizem 215/175.packages/types/src/meta-tool-definitions.ts: adescriptiondeWALLET_DEFINITIONpassa a ser o texto servido emapi.codespar.dev/meta-tools.jsoncopiado verbatim, não parafraseado. Só prosa;WALLET_INPUTnão muda.packages/cli/src/commands/wallet.ts:WalletCurrencyganhaoverspent: boolean(nome do openapi servido) e a colunaavailableimprime-2500 (overspent)quando a API devolveoverspent: true; a nota de rodapé explica a marca. Mínimo: nenhuma coluna nova, nenhum exit code novo.@codespar/types0.10.16 → 0.10.17 (commit próprio). O portãocheck-publish-driftdo CI reprovou: a prosa deWALLET_DEFINITIONmuda o conteúdo empacotado e 0.10.16 já está no registry, que é imutável. Mesmo rito de fix(release): bump @codespar/types para 0.10.16 e recusa conteudo que muda sem bump (core#129) #133 (package.json, package-lock.json,versiondeDEMO_SCENARIO_MANIFEST; sem entrada de CHANGELOG).@codespar/types0.10.14 (ent#933) e não é tocado aqui.Evidência
node scripts/openapi-spec.mjs check(packages/core, COM rede: servido == snapshot 35fddb5dbab7, 215 operações)npx vitest runempackages/core(19 arquivos, 190 testes; incluiopenapi-snapshot.test.tse o pino de 215 emapi-client.test.ts)npx vitest runempackages/types(150 testes; incluimeta-tool-definitions.test.tse a conformance)npx vitest runempackages/cli(31 testes)npx turbo run typecheck --filter=@codespar/sdk --filter=@codespar/types --filter=@codespar/cli --filter=@codespar/api-types(6 tasks)npx turbo run build(18 tasks) +node scripts/check-publish-drift.mjs(17 pacotes: types 0.10.17 "not on the registry — bump pending"; sdk, cli, mcp e adapters idem; api-types 0.5.0 é o waiver pré-existente de core#132)node --test scripts/check-publish-drift.test.mjsNão rodei
turbo run build typecheck testno monorepo inteiro (memória da máquina); CI cobre.grep -rn 'floored at 0' packagesdevolve zero.Fica de fora
packages/api-typesnão tem ocorrência de "floored": o gerado que a issue cita (~11689/11722) épackages/core/src/generated/openapi.ts, tratado aqui.codespar_walletem@codespar/types; o drift de resultado tipado continua sendo ent#933 / ent#1199.overspent; só a marca na célula./v1/feesentra no cliente gerado sem exemplo ou doc de uso; é o que o servido publica.🤖 Generated with Claude Code