Validação de publicações¶
Tradução informativa da versão em inglês, atualizada em 10/11/2026. Em caso de divergência, prevalece a versão em inglês. Identificadores, comandos e blocos de código são mantidos sem tradução.
Instala os pacotes DCP publicados a partir de seus registros oficiais em consumidores temporários,
verifica sua identidade e sua origem e executa o kit externo de conformidade já existente
(tools/conformance) contra o que foi instalado. Não
acrescenta nenhum catálogo, oráculo ou modelo de conformidade. O guia para leitores é
docs/conformance/RELEASE_VALIDATION.md; este arquivo
é a referência para executar e manter a ferramenta.
Um PASS afirma apenas isto: estes artefatos, com estes hashes, obtidos destes registros, passaram nestes catálogos com o kit nesta revisão e neste ambiente. Não é uma certificação e não cobre nada que não tenha sido executado.
node tools/release-validation/run.mjs [--list] [--lane <id>]... [--catalog <id>]...
[--resolution locked|fresh] [--work <dir>] [--clean]
[--python <exe>] [--go <exe>] [--cargo <exe>] [--chrome <exe>]
node --test tools/release-validation/release-validation.test.mjs # no external network
| Saída | Significado |
|---|---|
| 0 | PASS: todos os lanes, todas as células de catálogo (60), todos os blocos (3) e todos os controles |
| 1 | uma discrepância: um artefato respondeu e a expectativa versionada não coincide |
| 2 | uso |
| 3 | erro: de infraestrutura, de identidade, não suportado, incompleto ou de implementação |
| 4 | PARTIAL: uma seleção sem falhas. Um diagnóstico, nunca uma validação |
Um erro nunca conta como rejeição e prevalece sobre uma discrepância. Instalar um pacote e comprovar sua origem não é conformidade: apenas as células e os blocos são.
Os testes não usam rede externa; um deles serve um índice de pacotes a partir de um servidor HTTP de loopback.
Requisitos¶
Node 20 ou posterior em um host POSIX, com o npm que está ao lado de node, /bin/ps, /usr/bin/tar e, para o lane de Go, /usr/bin/unzip. Apenas módulos integrados do Node.
Python, Go, Rust e Chrome são ferramentas do host: o caminho indicado com --python, --go, --cargo ou --chrome, ou o primeiro python3, go ou cargo no PATH próprio da ferramenta (o diretório de node, /usr/bin, /bin) e o Chrome habitual da plataforma. Nada é instalado, atualizado ou trocado: GOTOOLCHAIN=local, e um proxy do rustup é recusado; portanto, passe o cargo do próprio toolchain (rustup which cargo), que deve ter rustc ao lado. O Python deve ser 3.11 ou posterior (o lane sdist em modo fresh lê pyproject.toml com tomllib); os locks versionados são para CPython 3.12.
Lanes¶
| Lane | Artefato publicado | Evidência |
|---|---|---|
npm-sdk |
npm @dcp-ai/sdk 2.4.0 |
8 catálogos |
npm-wasm-node |
npm @dcp-ai/wasm 2.2.0, no Node |
6 catálogos |
npm-wasm-browser |
o mesmo pacote, API pública em um Chrome real | 6 catálogos e o bloco direct-sections (103 casos) |
npm-cli |
npm @dcp-ai/cli 2.2.0 |
sem catálogo: os blocos cli-suites (218 testes) e cli-negative-control |
pypi-wheel |
PyPI dcp-ai 2.11.0, wheel |
8 catálogos |
pypi-sdist |
PyPI dcp-ai 2.11.0, sdist, construído aqui |
8 catálogos |
go |
módulo Go sdks/go/v2 v2.14.0 |
8 catálogos |
crates-default |
crates.io dcp-ai 2.14.0, features padrão |
8 catálogos |
crates-otlp |
o mesmo crate com a feature otlp |
8 catálogos |
Seis versões publicadas e nove lanes. Seis lanes executam 8 catálogos e os dois lanes de WebAssembly
executam 6: 60 células de catálogo, mais 3 blocos. manifest.json contém as identidades esperadas (as do dossiê de auditoria), os catálogos e os lanes; expected-blocks.json
contém os identificadores que cada bloco deve informar.
O que uma execução faz¶
- Diretório de trabalho. Um diretório
mkdtempnovo, ou--work <dir>, que não deve existir e deve estar fora do checkout. Nada é reutilizado. - Ambiente. Cada subprocesso recebe um ambiente construído pela ferramenta.
HOME,TMPDIR, os caches e as configurações de cada gerenciador de pacotes ficam dentro do diretório de trabalho. OPATHnão: é a lista curta, própria da ferramenta, de diretórios do host (o diretório denode,/usr/bin,/bine o diretório de um toolchain indicado por opção). Nada é herdado: nem credenciais, nem proxy, nem configuração do usuário ou do projeto. - Instalação, identidade e origem, por ecossistema (abaixo). Para cada pacote, três valores devem coincidir: o do manifesto, o hash que a ferramenta calcula sobre os bytes baixados e o que o gerenciador de pacotes ou o registro grava. O arquivo baixado deve ser igual, arquivo por arquivo e nos dois sentidos, ao que é instalado ou compilado, exceto os poucos arquivos que um instalador escreve por conta própria, nomeados abaixo.
- Células. Uma execução do runner do kit por lane e catálogo, com o wrapper do próprio kit
apontando para o pacote instalado. Um relatório só conta se for desta execução: um caminho que não
existia, uma execução dentro do intervalo observado com um identificador ainda não visto, o argv,
kit.catalog_sha256, os hashes dos fixtures, as suítes e os identificadores de caso que foram pedidos, e um status coerente com o código de saída do runner. Cada caso deve ter um status que sua classe permita, e as contagens de cada suíte e do relatório (incluindoexecuted) devem ser as dos casos. - Blocos. A evidência que não é uma célula de catálogo (as suítes da CLI, seu controle negativo, as seções diretas do navegador) é um bloco do seu lane. A campanha exige cada bloco exatamente uma vez, como uma célula: a instalação ou o status de um lane nunca o substituem.
- Controles, na mesma execução. Cada lane que traz controles (
npm-sdk,npm-cli,npm-wasm-browser) deve o seu próprio conjunto fixo sempre que é selecionado: exatamente um resultado comok === truepara cada identificador e nenhum outro, ou a campanha é um erro. Alguns são positivos que devem passar. Os demais são cópias alteradas das saídas reais (resultados ausentes, duplicados e desconhecidos, relatórios antigos, contagens incoerentes, identidades alteradas e origens locais) e execuções reais (uma expectativa invertida que deve falhar, falhas do wrapper e do harness, prazos esgotados, um processo deixado para trás, uma execução do navegador interrompida de fora), cada um dos quais deve ser recusado da maneira que lhe é exigida. - Relatório.
report.jsonno diretório de trabalho, com os relatórios do runner, as saídas dos blocos, os logs, os arquivos baixados e os locks. Tudo é mantido;--cleanremove apenas os consumidores instalados e os caches desta execução.
Um processo só conta como executado se todas as suas observações estiverem limpas (sem sinal, sem prazo esgotado, sem limite de saída, sem deixar nada para trás); o código de saída 0 sozinho não basta. Cada fase tem um prazo, e o supervisor interrompe toda a árvore de processos, quaisquer que sejam seus grupos de processos.
npm (npm-sdk, npm-wasm-node, npm-wasm-browser, npm-cli)¶
Um único consumidor (consumers/npm) com os três pacotes em versões exatas e um lock. Verificação estática do consumidor e do lock (versões exatas, todas as entradas de registry.npmjs.org com integridade sha512, sem links, cada pacote DCP uma única vez); npm pack em um cache vazio, com o hash calculado pela ferramenta; npm ci --ignore-scripts (locked) ou npm install --ignore-scripts (fresh); e depois o arquivo contra a árvore instalada.
@dcp-ai/wasm 2.2.0 contém pkg/package.json com o nome e a versão do crate Rust a partir do qual foi compilado (dcp-ai 2.14.0). O relatório registra as duas identidades e nunca espera que sejam iguais.
npm-cli. As quatro suítes decli/__tests__e onegative-control.mjsdo kit são executados contra a CLI instalada (DCP_CLI_ROOT): seu binário, seulib/e suas dependências tal como o npm as instalou. Antes, a ferramenta verifica que a CLI instalada carregaria o único@dcp-ai/sdkverificado. O veredito do próprio controle negativo não é aceito sem verificação: sua saída deve mostrar exatamente os quatro casos invertidos falhando, cada um em uma asserção, e os outros 214 passando.npm-wasm-browser. Owasm-browser.mjsdo kit aciona a API públicaDcpWasmem um Chrome real com um perfil temporário. Os arquivos que ele serve ficam fixados a umSHA256SUMSque a ferramenta escreve a partir da árvore instalada. Cada célula e as seções diretas devem vir com o registro de navegador que o próprio wrapper escreve daquela execução: novo, com o identificador da execução, dentro do intervalo observado, servindo os arquivos instalados, com um navegador que respondeu, encerrou e cujo perfil foi removido.
Python (pypi-wheel, pypi-sdist)¶
Cada lane tem seus próprios ambientes virtuais. O Python é executado com -I -B, e o pip com --isolated, sem cache, sem arquivo de configuração e com https://pypi.org/simple/ como único índice.
- Wheel.
pip download; o SHA-256 é verificado antes de instalar qualquer coisa;pip install --only-binary :all:; o relatório do próprio pip dá o hash que ele instalou e mostra que todos os arquivos vieram defiles.pythonhosted.org. O wheel e a distribuição instalada são comparados nos dois sentidos: cada arquivo de módulo deve ser igual e nada mais pode estar instalado. Ficam de fora apenas os arquivos que o pip escreve por conta própria em*.dist-info:RECORD(reescrito),INSTALLEReREQUESTED; umdirect_url.jsoné um problema aqui. Não existe bytecode quando as árvores são comparadas (--no-compile,-B).import dcp_aideve carregar daquele site-packages, sem user site e sem nenhum caminho fora do diretório de trabalho e do interpretador. - Sdist. O arquivo é primeiro baixado como bytes por
fetch-pypi.mjs, que não desempacota nem executa nada, e seu SHA-256 é comparado com o manifesto; o que o índice declara sobre ele é registrado à parte e não é dado como confiável. Só então ele é desempacotado, as dependências de build são instaladas e o wheel é construído a partir do arquivo local com--no-index --no-build-isolation. O wheel construído é um artefato separado no relatório; seus arquivos de módulo devem ser exatamente os do sdist, nos dois sentidos. Ele é instalado em um ambiente limpo e comparado como no lane do wheel, exceto que alidirect_url.jsoné o esperado (um wheel local foi instalado) e deve apontar para dentro do diretório de trabalho. - Locks.
consumers/python/<platform>/contémwheel.txt(o wheel e suas dependências de execução),sdist.txtebuild.txt(as dependências de build), todos fixados com hashes, eplatform.json. Há um diretório por plataforma, escolhido pelo interpretador em execução:cpython-3.12-darwin-arm64ecpython-3.12-linux-x86_64. Em qualquer outro interpretador ou plataforma,lockedé recusado como não suportado em vez de ser tentado.
Go (go)¶
O main.go do kit, byte a byte, com os go.mod e go.sum versionados (consumers/go, sem replace), compilado com -mod=readonly, GOPROXY=https://proxy.golang.org, GOSUMDB=sum.golang.org, GOENV=off e GOWORK=off. O módulo é primeiro baixado fora de qualquer módulo, de modo que o go o verifica contra o banco de dados de checksums. A ferramenta calcula h1 por
conta própria sobre os arquivos do zip baixado, compara o zip com o diretório a partir do qual o go
compila e lê as informações de build do binário: o módulo, sua versão e seu h1, sem nada substituído.
O relatório registra a arquitetura para a qual a ferramenta go compila, que não precisa ser a do host.
Rust (crates-default, crates-otlp)¶
O src/main.rs do kit, byte a byte, em dois consumidores com seus próprios Cargo.toml e Cargo.lock (consumers/rust-default, consumers/rust-otlp), CARGO_HOME e diretório target. O Cargo.toml do consumidor deve ser, linha a linha, o único manifesto que a ferramenta espera. Depois de cargo fetch --locked, o .crate tem seu hash calculado e comparado com o manifesto e com o lock antes de qualquer compilação; cargo metadata deve dizer que dcp-ai vem de crates.io, de um diretório dentro do diretório de trabalho, com as features do lane; o .crate é comparado nos dois sentidos com esse diretório, exceto o marcador .cargo-ok do próprio cargo; e depois cargo build --locked.
locked e fresh¶
locked(o padrão): as dependências de terceiros vêm dos locks versionados. A seleção é reproduzível; as ferramentas do host (npm, pip, Go, cargo, rustc, o compilador C, Chrome) não são fixadas, apenas registradas.fresh: resolvidas agora, como um consumidor novo as obteria. Os locks resultantes são mantidos no diretório de trabalho:package-lock.fresh.jsonna raiz e os demais emlocks/. No lane sdist, as dependências de build ficam então registradas, mas não fixadas.
As seis identidades DCP são exigidas nos dois modos.
CI¶
.github/workflows/release-validation.yml executa a ferramenta em ubuntu-24.04, manualmente (workflow_dispatch) ou em PRs que alterem esta ferramenta ou as entradas do kit de conformidade. Os PRs executam a campanha completa (all, locked), com acesso de leitura ao repositório, sem segredos e sem nenhuma etapa de publicação. Suas entradas são um lane (all ou um lane) e a resolução. ci.mjs decide o resultado: PASS apenas para all com saída 0; PARTIAL, informado como diagnóstico, apenas para um lane com saída
4; qualquer outra combinação faz o job falhar. O diretório de trabalho é enviado como artefato em todos
os casos.
Atualização para outra publicação¶
Esta ferramenta não publica nada. Depois que uma versão tiver sido publicada e seus identificadores registrados no dossiê de auditoria:
- Coloque as novas versões e os novos identificadores em
manifest.jsone no teste que os congela. Os nomes de arquivo de um pacote (file) e a versão interna do crate de@dcp-ai/wasmmudam junto. - Atualize os consumidores: as versões em
consumers/npm/package.json,consumers/go/go.mode nos doisCargo.toml, e as linhas DCP dos locks de Python. - Regenere os locks com
--resolution freshem cada plataforma que tenha um, e copie-os do diretório de trabalho:package-lock.fresh.json, que fica na raiz, elocks/python/<platform>/,locks/go/,locks/rust-default/elocks/rust-otlp/. Um lock de Python só é produzido executando naquela plataforma. - Se as suítes da CLI ou as seções diretas do kit mudaram, atualize
expected-blocks.json. - Execute os testes e depois a campanha completa em modo
locked.
Limites¶
- Uma campanha é um ambiente. O que foi executado está no guia; nada além disso é afirmado.
- Os wrappers e o runner são os do checkout; o pacote em teste é o do registro. A resolução de módulos não é instrumentada: a verificação de origem é o lock, a árvore instalada e o caminho que o wrapper informa.
- Os locks de Python cobrem duas plataformas. Os hashes que um lock contém não dizem nada sobre nenhuma outra.
- A versão e o user agent do navegador são registrados como o navegador os informa. O user agent é reduzido e não indica qual arquitetura foi executada.
- A limpeza de processos lê a tabela de processos. Um processo desvinculado por um pai que morre dentro
de um intervalo de sondagem (250 ms) não é visto, nada é limpo se a própria ferramenta for morta com
SIGKILL e, se
psfalhar, nenhum sinal é enviado e o relatório diz que a limpeza não foi confirmada. Uma execução do navegador interrompida de fora deixa seu perfil temporário no diretório de trabalho. - Sem proxy: o ambiente construído para os subprocessos não tem nenhuma variável de proxy.
- O relatório contém caminhos absolutos do host e nenhum valor de ambiente além dos que a ferramenta construiu.