fix(types): recipient publica a união que o contrato sempre exigiu (core#128) - #148
Merged
Merged
Conversation
…ore#128) `codespar_pay.recipient` era publicado como `type: "string"` e a própria descrição mandava passar um OBJETO de conta bancária, com a frase "the tool-call argument type, not this schema's declared string type, is what determines routing". O schema contradizia a si mesmo. Prosa não conserta schema. Cliente que ENFORÇA o declarado — MCP com validação estrita, OpenAI strict tool mode, gateway com ajv — rejeita o objeto antes de sair da máquina, ou coage para string, que a mesma descrição proíbe. O Pix cash-out MANUAL e o destino do method=ted ficam indisponíveis exatamente nos clientes bem-comportados. Medido hoje no documento servido (`GET /v1/meta-tools.json`): `recipient` seguia `type: "string"`. De carona, medi também que o enum de `action` JÁ foi ampliado (18 ações, com dict_*, dda_* e boleto_quote) — aquela metade da divergência fechou antes. **A união vai para o schema.** `MetaToolInputProperty` ganha `anyOf` e o `type` vira opcional; exatamente um dos dois está presente. `anyOf` e não array de tipos porque é o que os modos estritos aceitam na prática. O ramo de objeto declara `bank`, `account`, `branch`, `tax_id`, `name` e `account_type` CAMPO A CAMPO, onde antes havia uma frase. **A maquinaria de conformidade passa a enxergar união**, que é o trabalho de verdade: o caminhante de prosa e o de vocabulário descem nos ramos (uma descrição dentro de um ramo era invisível para todo portão de prosa), e o comparador entre runtimes compara assinatura de forma em vez do campo `type` cru — que, com os dois lados em `anyOf`, seria `undefined` dos dois lados e passaria calado. Três controles, e um deles achou defeito meu: - runtime que achata a união de volta para um tipo -> reprovado, com a mensagem nomeando as duas formas; - ramos escritos em outra ordem -> passa (união é conjunto, e portão que reprova reordenação é portão que se aprende a ignorar); - runtime que TIRA um campo do ramo de objeto -> passava calado. O comparador só olhava `properties` do nível de cima, e os campos de uma união moram um nível abaixo. Era o mesmo buraco, mais fundo. Na CLI, `--arg` aceita as duas formas pelo mesmo flag. O teste que escrevi pegou outro defeito meu: uma chave Pix de 11 dígitos é JSON válido, então o parse cru transformava um CPF no número 12345678901. O parse só vale quando cai num ramo estruturado. Versões: types minor (o `type` opcional é quebra de tipo para quem lia como `string`, e a forma de `recipient` muda), sdk minor porque reexporta o contrato, cli minor pela mudança de comportamento do `--arg`, adapters em patch só pela faixa de peer. FICA DE FORA, e é metade do conserto: o `codespar-enterprise` publica o seu próprio `input_schema` em `meta-tools.ts:136`, e é dele que sai o `/v1/meta-tools.json` servido. Enquanto ele não subir a mesma união (e o pino de `@codespar/types`, hoje em 0.10.14, três versões atrás), o documento que os clientes leem continua dizendo `type: "string"`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
O `@codespar/mcp` declara `@codespar/sdk` em dependencies E em peerDependencies, e o 0.5.6 foi publicado com as duas discordando: a dependência aceitava ^0.14.0 e o peer parava em ^0.13.0. O script de release que alargou "a faixa" achou uma das duas e deixou a outra — que é o que acontece com um fato escrito em dois lugares. O erro é meu, da rodada do v0.14.0. Impacto medido, para não vender por mais do que é: `npm i @codespar/mcp@0.5.6` instala limpo, e com `--strict-peer-deps` também — o npm deixa a dependência direta satisfazer o peer. O defeito é um pacote que se descreve errado, e quem lê a metadata (um resolvedor que não seja o npm, uma pessoa decidindo o que pinar) recebe a resposta errada dele. Portão novo, hermético e com controle próprio, rodando no ci.yml ao lado do self-test do portão de deriva: pacote que nomeia @codespar/sdk nos dois campos tem de nomear a MESMA faixa. Plantado o desalinhamento de volta, ele reprova; alinhado, passa. 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.
Closes #128.
codespar_pay.recipientera publicado comotype: "string"e a própria descrição mandava passar um OBJETO de conta bancária — com a frase "the tool-call argument type, not this schema's declared string type, is what determines routing". O schema contradizia a si mesmo, por escrito.Prosa não conserta schema. Um cliente que ENFORÇA o declarado — MCP com validação estrita, OpenAI strict tool mode, um gateway rodando ajv — rejeita o objeto antes de sair da máquina, ou coage para string, que a mesma descrição proíbe. O Pix cash-out MANUAL e o destino do
method=tedficam indisponíveis exatamente nos clientes bem-comportados.Medido antes de mexer
No documento que a produção serve, hoje:
A segunda linha é uma boa notícia que eu não procurava: o enum de
actionjá foi ampliado. A metade "ações vivas que o schema não anuncia" do levantamento de 08/09 fechou antes. Sobrou esta.A união vai para o schema
MetaToolInputPropertyganhaanyOf, etypevira opcional — exatamente um dos dois está presente.anyOfe não array de tipos porque é o que os modos estritos aceitam na prática. O ramo de objeto declarabank,account,branch,tax_id,nameeaccount_typecampo a campo, onde antes havia uma frase entre chaves.O trabalho de verdade foi ensinar a conformidade a enxergar união
typecru. Com os dois lados emanyOf, comparartypedariaundefineddos dois lados e passaria calado.Três controles, e um achou defeito meu
O terceiro era buraco real: o comparador só lia
propertiesdo nível de cima, e os campos de uma união moram um nível abaixo, dentro dos ramos. Mesmo buraco, mais fundo. Fechado na mesma mudança.E o teste que escrevi para a CLI pegou outro defeito meu: uma chave Pix de 11 dígitos (um CPF) é JSON válido, então o parse cru transformava a chave no número
12345678901. O parse agora só vale quando cai num ramo estruturado.FICA DE FORA — e é metade do conserto
O
codespar-enterprisepublica o seu próprioinput_schemaempackages/api/src/meta-tools.ts:136, e é dele que sai o/v1/meta-tools.jsonque os clientes leem. Enquanto ele não subir a mesma união, o documento servido continua dizendotype: "string".Esse PR tem duas dependências, nesta ordem: este aqui publicado, e o pino de
@codespar/typesno enterprise — hoje em 0.10.14, três versões atrás do que está no npm — subindo junto, porque o teste de conformidade de lá compara contra as definições publicadas.Versões
typesminor (otypeopcional é quebra de tipo para quem o lia comostring, e a forma derecipientmuda),sdkminor porque reexporta o contrato,climinor pela mudança de comportamento do--arg, e os 13 adapters em patch só pela faixa de peer. 18 suítes verdes, typecheck limpo, portão de deriva sem deriva nova.🤖 Generated with Claude Code