Skip to content

fix: superfície pós-lane wallet (snapshot OpenAPI com overspent, prosa de codespar_wallet, CLI marca overspent) - #141

Merged
fabianocruz merged 3 commits into
mainfrom
fix/surface-wallet-lane-ent1208
Sep 10, 2026
Merged

fix: superfície pós-lane wallet (snapshot OpenAPI com overspent, prosa de codespar_wallet, CLI marca overspent)#141
fabianocruz merged 3 commits into
mainfrom
fix/surface-wallet-lane-ent1208

Conversation

@fabianocruz

@fabianocruz fabianocruz commented Sep 10, 2026

Copy link
Copy Markdown
Member

Metade codespar-core de codespar/codespar-enterprise#1208 (lane wallet, PR codespar/codespar-enterprise#1204; drift de prosa é o de codespar/codespar-enterprise#933).

O que muda

  • Snapshot do OpenAPI re-buscado do servido (node scripts/openapi-spec.mjs refresh em packages/core, que é o mecanismo do repo: servido → openapi-snapshot.jsonsrc/generated/openapi.ts + operations.ts), commit próprio. As duas ocorrências de "floored at 0" no gerado somem e overspent: boolean aparece em GET /v1/consumers/{id}/wallet. O diff por caminho: esse, POST /v1/wallets/{id}/ledger (mandate_id/attempt_id/external_ref opcionais, ent#1194), /v1/sessions/{id}/execute|proxy_execute|send (texto do 403) e dois caminhos NOVOS, /v1/fees e /v1/fees/movimentar. A tabela de operações vai de 213 a 215, então o pino em api-client.test.ts sobe 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: a description de WALLET_DEFINITION passa a ser o texto servido em api.codespar.dev/meta-tools.json copiado verbatim, não parafraseado. Só prosa; WALLET_INPUT não muda.
  • packages/cli/src/commands/wallet.ts: WalletCurrency ganha overspent: boolean (nome do openapi servido) e a coluna available imprime -2500 (overspent) quando a API devolve overspent: true; a nota de rodapé explica a marca. Mínimo: nenhuma coluna nova, nenhum exit code novo.
  • @codespar/types 0.10.16 → 0.10.17 (commit próprio). O portão check-publish-drift do CI reprovou: a prosa de WALLET_DEFINITION muda 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, version de DEMO_SCENARIO_MANIFEST; sem entrada de CHANGELOG). ⚠️ Uma tag de release publica TODOS os pacotes não-private juntos: a próxima tag leva types 0.10.17 junto com sdk 0.12.0, cli 0.6.1, mcp 0.5.4 e os adapters 0.4.2, que o portão já listava como "bump pending". O enterprise pina @codespar/types 0.10.14 (ent#933) e não é tocado aqui.

Evidência

Portão rc
node scripts/openapi-spec.mjs check (packages/core, COM rede: servido == snapshot 35fddb5dbab7, 215 operações) 0
npx vitest run em packages/core (19 arquivos, 190 testes; inclui openapi-snapshot.test.ts e o pino de 215 em api-client.test.ts) 0
npx vitest run em packages/types (150 testes; inclui meta-tool-definitions.test.ts e a conformance) 0
npx vitest run em packages/cli (31 testes) 0
npx turbo run typecheck --filter=@codespar/sdk --filter=@codespar/types --filter=@codespar/cli --filter=@codespar/api-types (6 tasks) 0
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) 0
node --test scripts/check-publish-drift.test.mjs 0

Não rodei turbo run build typecheck test no monorepo inteiro (memória da máquina); CI cobre. grep -rn 'floored at 0' packages devolve zero.

Fica de fora

  • packages/api-types não tem ocorrência de "floored": o gerado que a issue cita (~11689/11722) é packages/core/src/generated/openapi.ts, tratado aqui.
  • Não há tipo de resultado para codespar_wallet em @codespar/types; o drift de resultado tipado continua sendo ent#933 / ent#1199.
  • A CLI não muda exit code nem cor para overspent; só a marca na célula.
  • /v1/fees entra no cliente gerado sem exemplo ou doc de uso; é o que o servido publica.

🤖 Generated with Claude Code

fabianocruz and others added 3 commits September 10, 2026 07:34
…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
fabianocruz merged commit 15301fb into main Sep 10, 2026
11 checks passed
@fabianocruz
fabianocruz deleted the fix/surface-wallet-lane-ent1208 branch September 10, 2026 13:15
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>
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