Pular para conteúdo

AUR-411 — Perfil polyglot toolchain offline (polyglot-toolchain-v1)

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

Este documento descreve a entrada polyglot-toolchain-v1 publicada no registry canônico de profiles. O card publica somente documentos: nenhum toolchain é instalado, nenhum pacote é baixado, nenhum runtime é invocado, nenhum engine é chamado e o runner permanece fail-closed para esta chave até um card de implementação dependente habilitá-la explicitamente.

Artefatos

Artefato Caminho
Schema .board/schemas/polyglot-toolchain-profile.schema.json
Profile .board/oci/profiles/polyglot-toolchain-v1.json
Lock .board/locks/oci/polyglot-toolchain-v1.lock.json
Registry .board/oci/profiles/registry.v1.json

O registry passa a publicar exatamente onze chaves, em ordem canônica crescente (LC_ALL=C sort, não estimada): bootstrap-readonly-v1, docs-tool-offline-v1, fake-provider-v1, fake-scm-offline-v1, go-git-offline-v1, go-unit-offline-v1, oci-conformance-v1, parser-worker-v1, polyglot-toolchain-v1, registry-v1, sqlite-offline-v1.

Em ordem C, polyglot- vem depois de parser- — a (0x61) precede o (0x6F) no primeiro byte que difere — e antes de registry-, porque p precede r. A chave ocupa portanto o índice 8 (base 0), e exatamente duas chaves deslocam uma posição à direita: registry-v1 (8→9) e sqlite-offline-v1 (9→10). Nenhuma chave existente muda de digest, schema ou lock.

Imagem pinada e o que ela realmente contém

O lock fixa Go e Bash na imagem de bootstrap já presente localmente, por digest imutável:

aurum-bootstrap-go-bash@sha256:503ac356fa6bca4bad56fade87b5479ff371bd446ffb9c7db91f211323c7c73e

A imagem foi escolhida por prova, não por conveniência. Sob --pull=never --network=none --read-only --user=65534:65534 --cap-drop=ALL (mais --security-opt=no-new-privileges e tmpfs limitado) o probe devolve bash 5.2.26, go1.21.13 e uma única interface de rede, lo.

Este é o resultado literal do probe, e é o limite honesto deste card. Das oito linguagens declaradas, a imagem de bootstrap carrega apenas bash:

Partição Runtime declarado bootstrap 503ac356… golang 4746d264…
bash bash presente (5.2.26) presente (5.2.15)
c-cpp cc ausente presente (cc, gcc, g++, ld, make)
python python3 ausente presente (sem pip/pip3)
javascript node ausente ausente
typescript tsc ausente ausente
dotnet dotnet ausente ausente
rust cargo ausente ausente
powershell pwsh ausente ausente

Nenhuma imagem local carrega as oito. Cinco runtimes — node, tsc, dotnet, cargo e pwsh — não existem em nenhuma das duas imagens pinadas do board. Este documento não afirma o contrário e o profile não finge que existem. Nenhuma imagem foi fabricada para este card e nenhum profile foi afrouxado para acomodar a ausência.

O probe registra também o que está presente e é honesto declarar: a imagem de bootstrap carrega os applets BusyBox wget e nc e o gerenciador de pacotes apk; a imagem golang carrega apt/apt-get, curl, wget, ssh, git e perl. Este plano não afirma que esses binários estão ausentes — afirma que não alcançam nada, porque network é none, egress é denied, HTTP_PROXY e HTTPS_PROXY são vazios, NO_PROXY é *, e porque instalar é negado em três campos independentes (toolchain_install, toolchain_bootstrap, package_install) mais o índice/registry de cada gerenciador fechado por variável de ambiente.

Por que a ausência é o contrato, e não uma lacuna tolerada

toolchain_materialization é pre-materialized e toolchain_install é denied. O profile pina cada partição já materializada e nunca cria uma. Um runtime que a imagem não carrega jamais pode passar a existir sob este plano, porque a única forma de obtê-lo seria uma instalação ou a rede, e as duas são negadas. A resposta correta a node ausente não é instalar node: é falhar fechado.

É exatamente isso que o loader faz. ResolveLanguageAUR411 recebe a disponibilidade medida e devolve:

Situação Código
runtime medido presente, partição pinada e isolada valid (plano offline isolado)
runtime medido ausente toolchain-unavailable
runtime nunca medido toolchain-unmeasured
linguagem fora do conjunto registrado language-unregistered
toolchain sem digest (plano ou partição) unpinned-toolchain / digest-invalid
plano rejeitado pelo loader o código da rejeição, nunca valid

toolchain-unmeasured existe porque ausência de evidência não é evidência de ausência nem de presença. Um runtime que ninguém mediu não é "indisponível" por padrão silencioso: é uma medição que não aconteceu, e tem código próprio.

Um card de implementação dependente que queira executar node, tsc, dotnet, cargo ou pwsh precisa primeiro fornecer uma imagem pinada por digest que já os carregue. Até lá o runner permanece fail-closed para esta chave, e as cinco partições resolvem toolchain-unavailable sob a imagem atual.

O que é medido, e o que a medição significa

Duas medições são executadas pela acceptance, e o texto abaixo não afirma nada além do que o mecanismo observa.

1. Disponibilidade de runtime. A acceptance mede, para cada um dos oito runtimes declarados, se o comando resolve através do PATH dentro da imagem de execução. A medição é feita no PATH intacto, antes de qualquer shim existir — do contrário um shim chamado cargo ou dotnet responderia ao probe e a medição relataria um toolchain inexistente. O que ela mede é resolução por PATH; um runtime alcançável apenas por caminho absoluto não apareceria nela, e nada aqui afirma o contrário.

O probe é falsificável nos dois sentidos por dois controles: aurum-control-present-411 é criado no PATH imediatamente antes da medição e precisa voltar present; aurum-control-absent-411 não pode existir e precisa voltar absent. Um probe fixado em qualquer das duas respostas falha em um dos controles com availability-probe-inert.

O ledger é lido em Go com falha dura em todos os modos de erro: variável não definida, arquivo ilegível, linha malformada, nome duplicado, nome faltando ou nome a mais param o teste. Um ledger ausente não é "nada estava disponível" e não é "tudo estava disponível" — é uma medição que não aconteceu.

Antes de qualquer medição real ser consultada, o argumento de disponibilidade é provado carregador com três medições sintéticas (todas presentes, todas ausentes, nada medido), que devem produzir valid, toolchain-unavailable e toolchain-unmeasured respectivamente. Se o argumento fosse ignorado as três concordariam e a medição real seria decorativa. Essas três não observam máquina nenhuma e este documento não as apresenta como observação.

2. Instalações de pacote. package_installs: 0 é uma afirmação de um documento JSON; o ledger é a medição que precisa concordar com ela. Vinte e cinco entrypoints de instalação e de gerenciamento de pacote — apk, apt, apt-get, pip, pip3, pipx, poetry, npm, npx, yarn, pnpm, corepack, bun, deno, tsc, dotnet, nuget, cargo, rustup, gem, bundler, conda, brew, curl, wget — são sombreados por shims à frente do PATH; cada shim registra o próprio nome e sai 70. O ledger é provado carregador antes de virar evidência de ausência: um shim é invocado de propósito e precisa aparecer, ou a execução falha com install-ledger-inert. Só então o ledger é zerado e exigido zero, e a camada Go recusa o plano se a contagem declarada e a medida divergirem. O que o mecanismo mede é uma invocação resolvida por PATH; um chamador que alcançasse um gerenciador de pacotes por caminho absoluto não apareceria nele.

Plano declarado

Limites e isolamento:

Chave Valor
network none
user 65534:65534
cap_drop / cap_add ALL / none
mounts / devices / sockets none / none / none
pull never
tmpfs rw,nosuid,nodev
checkout_readonly true
read_only_rootfs true
no_new_privileges true
privileged false
timeout_seconds 60
memory_mb 256
cpu_millis 1000
pids_limit 128
tmpfs_mb 128
stdout_limit_bytes / stderr_limit_bytes 65536 / 65536
max_input_files / max_input_bytes 10000 / 67108864
module_cache /go/pkg/mod (module_cache_read_only: true)

Capacidades polyglot, todas verificadas pelo loader:

Chave Valor Efeito
polyglot_root /tmp/aurum-polyglot todas as partições vivem no tmpfs limitado, nunca no checkout
toolchain_source digest-pinned um toolchain só é admitido content-addressed
toolchain_materialization pre-materialized o plano pina o que já existe e nunca cria
toolchain_install denied nenhum toolchain é instalado
toolchain_bootstrap denied nenhum runtime é provisionado
package_install denied nenhum pacote é instalado
package_registry denied nenhum índice/registry de pacote é alcançado
package_installs 0 declarado zero e medido zero por ledger
cache_mode read-only os package caches são somente leitura
telemetry denied nenhuma telemetria de runtime ou de gerenciador
egress denied nenhuma saída de rede
host_toolchain denied nenhum toolchain do host é usado
host_cache denied nenhum cache de pacote do host é montado
host_filesystem denied nenhum caminho do host é lido
subprocess denied nenhum subprocesso
unregistered_language denied uma linguagem fora do conjunto falha fechado
max_languages 8 teto exato de partições
max_packages 4096 teto exato de pacotes materializados
max_package_bytes 16384 teto exato por pacote
max_cache_bytes 8388608 teto exato por partição (8 MiB)
resolve_timeout_seconds 5 wall-clock por resolução de linguagem

As oito partições

Cada linguagem declara uma partição fechada de cinco campos, e o languages publicado e o objeto toolchains precisam descrever exatamente o mesmo conjunto — uma linguagem listada sem partição é uma partição que ninguém valida, e uma partição não listada é um plano alcançável por um nome que a lista não admite.

Linguagem Runtime Cache root
bash bash /tmp/aurum-polyglot/bash
c-cpp cc /tmp/aurum-polyglot/c-cpp
dotnet dotnet /tmp/aurum-polyglot/dotnet
javascript node /tmp/aurum-polyglot/javascript
powershell pwsh /tmp/aurum-polyglot/powershell
python python3 /tmp/aurum-polyglot/python
rust cargo /tmp/aurum-polyglot/rust
typescript tsc /tmp/aurum-polyglot/typescript

Cada partição carrega toolchain: "digest-pinned", cache_mode: "read-only" e telemetry: "denied", e a cópia por partição precisa concordar com a política global: uma política com uma partição isenta não é uma política.

O isolamento é imposto pelas constantes pinadas: cada partição precisa declarar exatamente o runtime que expectedRuntimes411 dá à sua linguagem e exatamente o cache_root polyglot_root + "/" + linguagem. Os mutantes shared-runtime e shared-cache-root são recusados por essas igualdades, que rodam primeiro.

O loader também exige que duas partições nunca compartilhem um cache_root nem um runtime, e que nenhum cache_root seja, contenha ou esteja fora de polyglot_root. Essas regras são defesa em profundidade, não a defesa: como expectedRuntimes411 é injetiva (oito runtimes distintos) e cada cache_root é derivado da própria linguagem (oito diretórios distintos), nenhum documento que passe pelas igualdades acima pode colidir. Foi verificado por deleção: removendo o bloco de distinção do loader, shared-runtime e shared-cache-root continuam negados e a acceptance continua saindo 0. Nenhum mutante atual isola essas regras, e este documento não as credita por rejeições que as constantes já fazem. Elas permanecem para o caso de um card dependente derivar runtime ou cache_root de outra fonte que não a tabela pinada.

Limites são constantes exatas, nunca mínimos

Todo limite é uma constante exata. max_cache_bytes: 0, max_cache_bytes: 34359738368 e max_cache_bytes: 33554432 são todos rejeitados, e o que os rejeita é a igualdade pinada max_cache_bytes == 8388608 — o mesmo vale para max_packages: 8192 contra max_packages == 4096. É por isso que aggregate-cache-overflow e oversized-package-set falham, e este documento não credita outra regra por essas rejeições.

O loader mantém, além das igualdades, um conjunto de relações de defesa em profundidade. Elas são de fato executadas em toda validação; o que hoje é inalcançável é a rejeição por elas, porque as igualdades pinadas rodam antes e já recusam todo documento que as violaria:

  • max_cache_bytes <= tmpfs_mb * 1024 * 1024;
  • len(languages) * max_cache_bytes <= tmpfs_mb * 1024 * 1024 — as oito partições coexistem no mesmo tmpfs limitado, então o teto somado precisa caber. Com os valores publicados, 8 * 8388608 é exatamente 67108864, metade dos 128 MiB;
  • max_packages <= max_input_files e max_packages * max_package_bytes <= max_input_bytes — com os valores publicados, 4096 * 16384 é exatamente max_input_bytes;
  • 0 < resolve_timeout_seconds <= timeout_seconds;
  • len(languages) <= max_languages.

Raízes ancoradas

polyglot_root e cada cache_root são validados por expressões ancoradas com \A e \z — início e fim do texto, nunca de uma linha. Por isso /workspace, uma travessia que começa dentro da raiz limitada e sai dela, uma barra final, um irmão parecido (…-host), um newline embutido, um newline final, um espaço à esquerda, uma grafia em maiúsculas, um homóglifo cirílico (U+043E no lugar do o) e a string vazia são todos negados igualmente.

Ambiente

O ambiente é fechado sobre exatamente vinte chaves. Cada uma fecha um canal real por onde um gerenciador de pacotes alcançaria um índice, um registry ou um endpoint de telemetria:

Chave Valor
GOPROXY / GOSUMDB / GONOSUMDB / GOTOOLCHAIN off / off / * / local
CGO_ENABLED 0
AURUM_POLYGLOT_ROOT /tmp/aurum-polyglot
PIP_NO_INDEX / PIP_DISABLE_PIP_VERSION_CHECK 1 / 1
NPM_CONFIG_OFFLINE / NPM_CONFIG_REGISTRY true / `` (vazio)
NUGET_PACKAGES /tmp/aurum-polyglot/dotnet
DOTNET_CLI_TELEMETRY_OPTOUT / DOTNET_NOLOGO 1 / 1
CARGO_HOME / CARGO_NET_OFFLINE /tmp/aurum-polyglot/rust / true
POWERSHELL_TELEMETRY_OPTOUT / POWERSHELL_UPDATECHECK 1 / Off
HTTP_PROXY / HTTPS_PROXY / NO_PROXY / / *

Essas variáveis são defesa em profundidade, não a defesa: a imagem pinada não carrega pip, npm, dotnet, cargo nem pwsh para configurar. Cada uma é pinada por constante, então reabrir qualquer uma delas é um mutante provado.

AURUM_POLYGLOT_ROOT, NUGET_PACKAGES e CARGO_HOME são a segunda grafia de diretórios que o plano também declara. O loader e a camada de integração exigem que as duas grafias concordem: se divergirem, um gerenciador de pacotes lê ou escreve um caminho que o plano nunca limitou.

Comandos permitidos

["go", "test", "-mod=readonly", "./..."]

Qualquer outro comando, ou o mesmo comando com aridade diferente, invalida o plano.

Constantes pinadas três vezes

As constantes de segurança do schema estão pinadas nos bytes do schema e, em Go, em profileSchemaConstants411, environmentSchemaConstants411 e toolchainSchemaConstants411, todas verificadas por validateSchemaInvariant411. Um revisor que afrouxe o documento de schema e recompute o schema_digest no registry move os dois arquivos de forma consistente e ainda assim falha, porque os mapas Go não se movem com eles.

Compatibilidade com as chaves vizinhas

Os dez loaders de registry já entregues fecham o allowlist de chaves sobre o conjunto registrado, e o loader de AUR-403 ainda afirma o comprimento do registry e a ordem posicional das chaves; a camada de contratos de AUR-404 afirma a aridade. Registrar uma décima primeira chave quebra essas regressões de fora deste card a menos que elas aprendam a chave no mesmo commit. Nenhuma delas lê os documentos polyglot: cada uma verifica apenas os caminhos declarados e o formato do digest, de modo que registrar o profile polyglot não pode re-apontar silenciosamente o plano Go unit, o Git, o fake provider, o parser worker, o SQLite, a documentação, o fake SCM ou o conformance.

A aridade de AUR-403 vai de dez para onze e a de tests/contracts/AUR-404.go também, ambas exatas e não relaxadas para mínimo: toda rejeição que faziam antes continua falhando, e cada chave continua afirmada no único índice que a ordem canônica ascendente lhe dá.

A aridade total do registry é afirmada em tests/unit/AUR-411.go, que este card possui, e deliberadamente não em tests/contracts/AUR-411.go: este card possui a chave polyglot-toolchain-v1, não o tamanho do registry.

Códigos de rejeição

Código Quando
profile-unregistered a chave não está no registry, ou o conjunto de chaves não é exatamente o registrado
duplicate-profile a chave aparece duas vezes
order-invalid as chaves não estão em ordem canônica ascendente
schema-invalid schema afrouxado, chave obrigatória ausente, campo desconhecido, JSON duplicado ou lixo no fim
schema-missing / profile-missing / lock-missing documento ausente ou não regular
schema-digest-mismatch / lock-digest-mismatch digest do registry não confere com os bytes
digest-invalid toolchain_source ou o toolchain de uma partição sem digest, ou digest malformado
mutable-input lock com tag mutável, nome sem digest ou outro repositório
unsafe-plan qualquer limite, negação, raiz, partição ou variável de ambiente fora do plano
credential-present forma de credencial em qualquer valor de ambiente
language-unregistered linguagem fora do conjunto registrado, ou impersonação de uma declarada
unpinned-toolchain resolução de partição cujo toolchain não é content-addressed
toolchain-unavailable runtime declarado, medido ausente na imagem de execução
toolchain-unmeasured runtime declarado, nunca medido — falha fechado, nunca assume

Diagnóstico offline

bash tests/acceptance/AUR-411.sh AC-001 executa a validação completa sem rede, sem engine, sem subprocesso de runtime e sem instalação. Seletores individuais (TestAUR411, ContractAUR411, IntegrationAUR411, E2EAUR411, AC-001-MUT-001, AC-001-MUT-002, AC-001-MUT-003) executam uma camada ou uma mutação isolada; um seletor desconhecido sai 64. Falha de ambiente sai 69 e nunca é confundida com RED comportamental.

Compatibilidade, migração, rollback

Profiles existentes conservam chave e digest; uma mudança incompatível cria uma nova chave versionada. A migração altera consumidores por card dependente e nunca redireciona uma chave existente. O rollback remove somente esta entrada e seus arquivos quando nenhum consumidor depender dela.