Skip to content

fix(release): bump @codespar/types para 0.10.16 e recusa conteudo que muda sem bump (core#129) - #133

Merged
fabianocruz merged 1 commit into
mainfrom
fix/core129-publish-drift-guard
Sep 6, 2026
Merged

fix(release): bump @codespar/types para 0.10.16 e recusa conteudo que muda sem bump (core#129)#133
fabianocruz merged 1 commit into
mainfrom
fix/core129-publish-drift-guard

Conversation

@fabianocruz

Copy link
Copy Markdown
Member

Fecha o core#129: @codespar/types bumpado para 0.10.16, e um guarda que recusa o estado que produziu o defeito. O guarda e a parte maior. Sem ele a proxima divergencia nasce igual e so aparece quando alguem for usar uma definicao que "existe".

O defeito, remedido nesta sessao

Nenhum numero abaixo veio da issue. Todos foram medidos hoje contra origin/main e registry.npmjs.org.

Versao declarada contra versao publicada:

$ git show origin/main:packages/types/package.json | grep '"version"'
  "version": "0.10.15",
$ npm view @codespar/types version
0.10.15
$ npm view @codespar/types time --json | tail -2
  "0.10.15": "2026-07-04T22:29:11.952Z"

Contagem de definicoes, dois instrumentos de cada lado. Lado do main, src compilado com o tsconfig real do pacote:

$ npx tsc -p packages/types/tsconfig.json
$ grep -oE '"codespar_[a-z_]+"' dist/meta-tool-definitions.js | sort -u | wc -l
15
$ node -e 'import("./dist/meta-tool-definitions.js").then(m=>console.log(Object.keys(m.SHARED_META_TOOL_DEFINITIONS).length))'
15

Lado do npm, tarball publicado daquela mesma versao:

$ npm pack @codespar/types@0.10.15 && tar -xzf codespar-types-0.10.15.tgz
$ grep -oE '"codespar_[a-z_]+"' package/dist/meta-tool-definitions.js | sort -u
"codespar_invoice"
"codespar_notify"
"codespar_pay"
$ node -e 'import("./package/dist/meta-tool-definitions.js").then(m=>console.log(Object.keys(m.SHARED_META_TOOL_DEFINITIONS).length))'
3

Byte a byte, mesmo arquivo: 8291 publicado contra 40660 no main. As 12 ausentes incluem codespar_wallet (cash-in, B.5 do roteiro homologado) e codespar_kyc (B.1). O controle positivo continua valendo: os dois instrumentos concordam nos dois lados, entao o 3 nao e artefato de formato.

Causa, confirmada na historia:

$ git log -1 --format='%cI %h %s' v0.10.15
2026-07-04T19:25:59-03:00 03f14d7 feat(types): AgenticReceipt ... (#112)

A tag v0.10.15 aponta para 03f14d7, que tinha 3 definicoes. A publicacao saiu 3 minutos depois. O 3436b7d (#126, 03/09) levou para 15 e deixou "version": "0.10.15" intacta. Dois meses de CI verde com o defeito vivo.

O guarda

scripts/check-publish-drift.mjs. Para cada pacote publicavel do workspace: se a versao declarada ja existe no registro, o conteudo empacotado tem que ser identico ao que foi publicado sob aquele numero. Se difere, reprova e exige bump. Versao que ainda nao esta no registro e o estado saudavel: o bump esta pendente e o conteudo vai junto com ele.

Por que esta forma e nao as outras duas. As tres foram consideradas contra o caso historico, que e o unico teste que importa:

  • Recusar publish se a versao ja existe no registro roda so no push de tag, ou seja depois do merge, e o loop do publish.yml trata "You cannot publish over the previously published versions" como soft-pass de proposito, para re-rodar uma tag ser idempotente. Transformar isso em falha dura quebra o re-run legitimo e mesmo assim nao teria pego este caso: entre 3436b7d e hoje nenhuma tag v* foi empurrada. O defeito viveu dois meses sem nunca chegar perto do publish.
  • Comparar a contagem de definicoes dos dois lados enxerga um arquivo de um pacote, e so a cardinalidade dele. Editar o schema de uma definicao sem mudar a contagem passa limpo, e essa e a proxima deriva mais provavel. Alem disso cola um conceito de dominio dentro de um portao de release.
  • Comparar conteudo publicado contra o do main nomeia o defeito exato: o que o consumidor consegue instalar difere do que o main produziria, sob o mesmo numero imutavel. Roda em PR, para todo pacote, e independe de ha quanto tempo a divergencia entrou. Um guarda amarrado ao diff do PR teria passado em todos os PRs depois do culpado.

O guarda tambem entrou como pre-flight no publish.yml, antes do loop. Ali ele fecha o buraco irmao: uma tag empurrada com versoes nao bumpadas hoje reporta "✓ already at this version", nao publica nada e sai verde.

Prova de que pega o caso historico

Rodado no main antes do bump, com o registro real:

$ node scripts/check-publish-drift.mjs
✗ @codespar/types@0.10.15 — content differs from the published 0.10.15, which is immutable — bump the version
    changed  dist/meta-tool-definitions.js  8291 -> 40660 bytes
    changed  dist/meta-tool-definitions.d.ts  2504 -> 6641 bytes
    changed  dist/types.d.ts  39734 -> 44260 bytes
    added    dist/meta-tool-definition-conformance.js  14093 bytes
    ...
$ echo $?
1

O guarda reproduz sozinho os dois numeros da issue (8291 e 40660) sem que nenhum deles esteja escrito no codigo dele. Depois do bump, o mesmo comando devolve ✓ @codespar/types@0.10.16 — 0.10.16 is not on the registry e sai 0.

Os controles

Nao-vacuidade, unitario. scripts/check-publish-drift.test.mjs, primeiro teste do arquivo: planta o estado exato do core#129 (conteudo mexido, versao intacta, versao presente no registro) e exige reprovacao. Para provar que a suite nao e vaca, quebrei o guarda de proposito (drifted: false fixo) e rodei:

$ node --test scripts/check-publish-drift.test.mjs
ℹ tests 10
ℹ pass 7
ℹ fail 3
✖ NON-VACUITY: fails when content moved and the version did not
✖ lets through the exact state it was written for
✖ does NOT cover the next drift in the same package

A mutacao mata o teste de nao-vacuidade. Guarda restaurado, 10/10 passam, em Node 25 e em Node 20 (a versao do CI).

Nao-vacuidade, ponta a ponta contra o registro real. Plantei uma divergencia num pacote intocado (@codespar/vercel), reconstrui e rodei o CLI de verdade:

$ node scripts/check-publish-drift.mjs --only @codespar/vercel
✗ @codespar/vercel@0.4.1 — content differs from the published 0.4.1 ... bump the version
    changed  dist/index.js  1628 -> 1729 bytes
$ echo $?
1

Controle do outro lado, 1: mudanca legitima com bump passa. Mesmissima mudanca de conteudo, agora com 0.4.1 -> 0.4.2:

$ node scripts/check-publish-drift.mjs --only @codespar/vercel
✓ @codespar/vercel@0.4.2 — 0.4.2 is not on the registry — bump pending, content will ship with it
$ echo $?
0

Controle do outro lado, 2: quem nao toca o pacote nao dispara nada. Sonda revertida:

$ node scripts/check-publish-drift.mjs --only @codespar/vercel
✓ @codespar/vercel@0.4.1 — identical to the published 0.4.1

Isso depende de o build ser reproduzivel, o que foi verificado antes de escolher o desenho: npm pack do main para um pacote intocado bate hash a hash com o tarball publicado, package.json incluso. Na varredura dos 17 pacotes publicaveis, 12 vieram identical sem nenhum falso positivo.

O que o bump NAO conserta

Isto e fato sobre o mundo, nao pessimismo.

Versao npm e imutavel. @codespar/types@0.10.15 resolve para 3 definicoes no registro e 15 no codigo, para sempre. Nao existe republicacao sobre aquele numero. Quem pinou 0.10.15 exato nao tem conserto nenhum ali: a unica saida e mover o pin. O 0.10.16 nao alcanca ninguem retroativamente, so quem instalar depois de publicado.

E o 0.10.16 ainda nao existe. Este PR muda um numero em package.json; ele nao publica. Enquanto ninguem empurrar a tag, npm i @codespar/types continua entregando 3 definicoes.

Consumidores dentro deste repo, medidos (npm view "@codespar/types@<range>" version):

Consumidor Pin Resolve hoje Depois de publicar 0.10.16
examples/payment-failure-triage 0.10.14 exato 0.10.14 0.10.14, nao muda
examples/boleto-expiry-fiscal-remediation 0.10.14 exato 0.10.14 0.10.14, nao muda
examples/service-invoice-meta-tool ^0.10.11 0.10.15 0.10.16
examples/installment-negotiation-meta-tool ^0.10.11 0.10.15 0.10.16
examples/latam-commerce-smoke ^0.1.0 0.1.0 0.1.0, nao muda

Ninguem neste repo pina 0.10.15 exato. Os dois pins exatos estao em 0.10.14, o que significa que esses exemplos tambem nunca viram as 15 definicoes e continuam sem ver depois deste bump ate alguem mexer no pin deles. Consumidores externos ao repo eu nao consigo enxergar: quem tiver 0.10.15 em lockfile esta sem conserto naquele numero.

Nao mede o lado Python. packages/python publica no PyPI por outro workflow e o guarda so olha npm.

Dois outros pacotes no mesmo estado

O guarda encontrou o mesmo defeito em mais dois pacotes. Nao bumpei nenhum dos dois porque escolher o proximo numero deles e decisao de release, nao de quem achou:

Os dois estao cobertos por waiver em scripts/publish-drift-baseline.json, e o waiver e fixado por fingerprint sha256 do par (tarball publicado + arvore local). Ele cobre um estado medido e mais nada: qualquer mudanca nova no pacote deixa de casar e o guarda reprova, e o bump torna o waiver obsoleto e tambem reprova ate ser removido. Ha teste para as duas expiracoes. Nao e botao de mudo.

Sem esses dois waivers o guarda nasceria vermelho no main e seria desligado na primeira semana.

O que o merge dispara, exatamente

Para quem for autorizar: o merge deste PR nao publica nada.

# .github/workflows/publish.yml
on:
  push:
    tags:
      - "v*"

A publicacao e disparada por push de tag v*, nunca por merge no main. O merge daqui roda o ci.yml (build, typecheck, test, self-test do guarda, guarda, audit) e mais nada. @codespar/types@0.10.16 so chega ao npm quando alguem empurrar uma tag v*, e ai o workflow roda com OIDC trusted publishing (id-token: write, sem token no repositorio), constroi, testa, passa pelo guarda como pre-flight e entao publica todos os pacotes publicaveis do workspace no mesmo loop. Nesse momento sai @codespar/types@0.10.16; @codespar/sdk e @codespar/api-types, ainda nos numeros ja publicados, cairao no soft-pass "already at this version" e nao publicarao nada, o que e o estado descrito no core#131 e no core#132.

Verificacao rodada localmente

npx turbo run build typecheck test   ->  54 successful, 54 total
  @codespar/types                    ->  Test Files 7 passed, Tests 150 passed
node --test scripts/...test.mjs      ->  10/10 (Node 25 e Node 20)
node scripts/check-publish-drift.mjs ->  17 pacotes, 0 drift novo, 2 waived, exit 0
npm audit --omit=dev --audit-level=high -> 0 vulnerabilities
lock file drift check (regra do CI)  ->  PASS

O bump exigiu mover DEMO_SCENARIO_MANIFEST.version junto, em packages/types/src/testing/scenario-manifest.ts: o scenario-manifest.test.ts ja prendia manifesto e package.json em lockstep. O repo ja tinha guarda para "publicou e esqueceu de bumpar o manifesto"; o que faltava era o inverso, que e o core#129. O package-lock.json foi regenerado com npm install --package-lock-only e mudou exatamente uma linha.

Nota de ambiente: o custo do guarda no CI e ~13s para os 17 pacotes, e ele le registry.npmjs.org. Registro ilegivel sai com codigo 2 e reprova o job de proposito, porque "nao consegui checar" nao pode ser lido como "esta limpo".

Fecha o core#129. Abre o core#131 e o core#132.

… changes without a version bump (core#129)

The bump alone would leave the next divergence free to happen the same
way, so the guard is the larger half of this change.

Measured on 06/09 against origin/main and registry.npmjs.org:
@codespar/types declared 0.10.15, the registry's latest was 0.10.15,
main's compiled meta-tool-definitions.js was 40660 bytes with 15
definitions, and the published tarball of that same version was 8291
bytes with 3. Missing: codespar_wallet, codespar_kyc and 10 others.

scripts/check-publish-drift.mjs compares every publishable package
against what is actually on the registry under its declared version and
fails when the content differs, because npm versions are immutable and
that content is unreachable until the number moves. It runs in CI on
every PR and again in publish.yml before the publish loop.

The guard found the same defect in two more packages, left at their
current numbers because choosing their next version is a release
decision: @codespar/sdk (core#131) and @codespar/api-types (core#132).
Both are waived by exact fingerprint in publish-drift-baseline.json, so
the waiver covers one measured state and expires the moment either side
moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fabianocruz

Copy link
Copy Markdown
Member Author

Estado do CI neste PR

mergeable: MERGEABLE, mergeStateStatus: UNSTABLE. O rollup de checagens nao esta vazio: 10 entradas, 6 verdes e 4 vermelhas, 0 pendentes.

SUCCESS  ci
SUCCESS  gitleaks
SUCCESS  smoke
SUCCESS  validate-example-skeleton
SUCCESS  validate-example-nfse-from-natural-language
SUCCESS  validate-example-whatsapp-installment-negotiation
FAILURE  validate-example-service-invoice-meta-tool
FAILURE  validate-example-installment-negotiation-meta-tool
FAILURE  validate-example-payment-failure-triage
FAILURE  validate-example-boleto-expiry-fiscal-remediation

Os 4 vermelhos sao pre-existentes no main, nao vieram deste PR. Controle, no run do proprio main em 3436b7d, que e o HEAD atual:

$ gh run view 33781722750 --json jobs
failure validate-example-installment-negotiation-meta-tool
failure validate-example-boleto-expiry-fiscal-remediation
failure validate-example-service-invoice-meta-tool
failure validate-example-payment-failure-triage
success ci
success validate-example-skeleton
success validate-example-nfse-from-natural-language
success validate-example-whatsapp-installment-negotiation

Mesmos 4 jobs, mesmo erro (Process completed with exit code 3), e nos dois casos o passo que quebra e o "Run the ... demo end-to-end", nao os passos de fixture nem o "Dual-runtime divergence gate (completeness + version-alignment)". Esses dois passam. Ou seja: a mudanca de DEMO_SCENARIO_MANIFEST.version para 0.10.16 nao e a causa — esses exemplos resolvem @codespar/types do npm por pin proprio, nao do workspace. Os demos end-to-end sobem ghcr.io/codespar/codespar:latest, que e outro problema conhecido e fora do escopo desta issue.

O job ci, que e onde o guarda entrou, esta verde, e os dois passos novos rodaram de verdade:

Publish drift guard — self-test
  ok 1 - NON-VACUITY: fails when content moved and the version did not
  ...
  # tests 10 / # pass 10 / # fail 0

Publish drift guard
  ! @codespar/api-types@0.5.0 — known pre-existing drift, waived ... pending core#132
  ! @codespar/sdk@0.11.0 — known pre-existing drift, waived ... pending core#131
  ✓ @codespar/types@0.10.16 — 0.10.16 is not on the registry — bump pending
  ✓ @codespar/hermes@0.4.1 — 0.4.1 is not on the registry — bump pending
  (12 outros: identical to the published <versao>)
  17 publishable package(s) checked, no new unbumped drift; 2 pre-existing waived.

Vale registrar um ganho de confianca de graca: os 12 identical sairam num runner ubuntu x64 com Node 20, enquanto a mesma medicao local rodou em macOS arm64 com Node 25. O npm pack do main bate hash a hash com o tarball publicado nas duas plataformas, entao a comparacao nao depende do ambiente do build e o guarda nao vai gerar falso positivo por nao-reprodutibilidade.

Este PR nao foi mergeado e nada foi publicado. O merge nao dispara publicacao (o publish.yml so roda em push de tag v*); a decisao de publicar e do Fabiano.

@fabianocruz
fabianocruz merged commit 4f6d21c into main Sep 6, 2026
6 of 10 checks passed
fabianocruz added a commit that referenced this pull request Sep 10, 2026
…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 10, 2026
…a de codespar_wallet, CLI marca overspent) (#141)

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.json` → `src/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 #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](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <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