AUR-408 — Perfil de ferramenta de documentação offline (docs-tool-offline-v1)
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
Este documento descreve a entrada docs-tool-offline-v1 publicada no registry
canônico de profiles. O card publica somente documentos: nenhum engine é
invocado, nenhum documento é gerado ou renderizado, nada sob docs/ é
produzido pelo profile 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/docs-tool-offline-profile.schema.json |
| Profile | .board/oci/profiles/docs-tool-offline-v1.json |
| Lock | .board/locks/oci/docs-tool-offline-v1.lock.json |
| Registry | .board/oci/profiles/registry.v1.json |
O registry passa a publicar exatamente oito chaves, em ordem canônica
crescente: bootstrap-readonly-v1, docs-tool-offline-v1, fake-provider-v1,
go-git-offline-v1, go-unit-offline-v1, parser-worker-v1, registry-v1,
sqlite-offline-v1. Nenhuma chave existente muda de digest, schema ou lock.
docs- ordena depois de bootstrap- e antes de fake-, então a nova chave
ocupa o índice 1 e as seis chaves seguintes deslocam-se uma posição à direita.
Nenhuma chave sai da lista, nenhuma aridade exata vira mínimo e nenhuma
igualdade vira desigualdade: cada chave continua asserida no único índice que a
ordem canônica lhe dá.
Imagem pinada
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) os três probes locais respondem:
| Imagem | Bash | Go | Ferramenta de documentação | Outros |
|---|---|---|---|---|
aurum-bootstrap-go-bash@sha256:503ac356… |
5.2.26 | go1.21.13 | pandoc, mkdocs, jekyll, hugo, asciidoctor, gomarkdoc, sphinx-build ausentes |
sem ruby, python3, node, cc, gcc, git; uma única interface, lo |
golang@sha256:4746d264… (imagem de go-git-offline-v1) |
5.2.15 | go1.21.13 | as mesmas sete ausentes | tem git, python3, cc/gcc; uma única interface, lo |
ghcr.io/actions/jekyll-build-pages@sha256:6791ebfd… |
presente | ausente | jekyll e ruby presentes |
sem Go |
Isso é o que determina o plano, e não o contrário. A única imagem local que
carrega uma ferramenta de site é a de Jekyll, e ela é rejeitada por duas razões
independentes: não tem Go, portanto não satisfaz o command declarado; e o
modelo de plugin do Jekyll é execução dinâmica de código Ruby em tempo de
build, exatamente o que este profile nega. Não existe imagem local que combine
Go com pandoc, mkdocs, hugo ou asciidoctor.
Portanto generator_cgo: false e CGO_ENABLED: "0" fixam um gerador e um
renderer puro Go, e native_site_tool: "denied" recusa explicitamente a
classe inteira de ferramentas nativas. A ausência dessas ferramentas na imagem
pinada não é uma lacuna tolerada — é exatamente o que o profile nega. Um
gerador ligado por cgo, ou um native_site_tool diferente de denied, é
rejeitado com unsafe-plan. Nenhuma imagem foi construída, baixada ou
fabricada para este card.
Somente aurum-bootstrap-go-bash@sha256:<64 hex> é aceito. Uma tag mutável
(aurum-bootstrap-go-bash:1.21), um nome sem digest ou outro repositório é
rejeitado com mutable-input antes de qualquer resolução de imagem.
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 da toolchain documental, todas verificadas pelo loader:
| Chave | Valor | Efeito |
|---|---|---|
generator |
digest-pinned |
o gerador só é admitido content-addressed |
generator_cgo |
false |
gerador puro Go: nenhuma lib nativa, nenhum site tool |
generator_root |
/tmp/aurum-docs-tool |
o gerador vive no tmpfs limitado |
renderer |
digest-pinned |
o renderer só é admitido content-addressed |
plugin_set |
digest-pinned |
o conjunto de plugins só é admitido content-addressed |
native_site_tool |
denied |
pandoc, mkdocs, jekyll, hugo, asciidoctor negados |
dynamic_plugin |
denied |
nenhum plugin é carregado ou resolvido em tempo de execução |
remote_fetch |
denied |
nenhum tema, asset, plugin ou fonte é buscado remotamente |
snippet_execution |
denied |
nenhum bloco de código do documento é executado |
snippet_languages |
["bash", "go", "json"] |
allowlist fechada de linguagens inertes |
fixture_source |
local |
as fixtures são locais, nunca remotas |
fixture_root |
/tmp/aurum-docs-fixtures |
as fixtures vivem no tmpfs limitado |
output_mode |
ephemeral |
o output existe apenas durante a execução |
output_root |
/tmp/aurum-docs-output |
o output vive no tmpfs limitado, nunca no checkout |
host_output |
denied |
nenhuma escrita fora do output efêmero |
host_filesystem |
denied |
nenhum caminho do host é lido ou escrito |
subprocess |
denied |
nenhum processo externo é criado |
max_documents |
64 |
tamanho exato do corpus admissível |
max_document_bytes |
1048576 |
teto exato por documento (1 MiB) |
max_output_bytes |
33554432 |
teto exato de output (32 MiB) dentro do tmpfs de 128 MiB |
render_timeout_seconds |
5 |
wall-clock por renderização |
generator, renderer e plugin_set pinam a exigência, não os bytes: o
gerador, o renderer, os plugins, os temas e qualquer documento renderizado são
não-objetivos deste card e pertencem ao card de implementação dependente. O que
este card garante é que uma ferramenta sem digest nunca é resolvida.
snippet_languages é um allowlist, comparado elemento a elemento: acrescentar
uma linguagem é mudança de plano e é rejeitado exatamente como habilitar
execução. snippet_execution: "denied" é independente disso — nenhuma
linguagem da allowlist é executada; elas apenas descrevem o que o renderer
emite como texto inerte.
Os limites são constantes exatas, nunca mínimos: max_output_bytes: 0 e
max_output_bytes: 34359738368 são ambos rejeitados, o primeiro por ser
ilimitado e o segundo por não caber no tmpfs que teria de segurá-lo. O loader
verifica ainda, de forma independente das constantes, que
max_output_bytes <= tmpfs_mb * 1024 * 1024 e que
max_documents * max_document_bytes <= max_input_bytes — um corpus ou um output
que não cabe no próprio limite teria de transbordar para fora dele. Com os
valores publicados, 64 * 1048576 = 67108864 é exatamente max_input_bytes.
generator_root, fixture_root e output_root são validados por expressões
ancoradas nas duas pontas (^/tmp/aurum-docs-output$ e análogas), então
/workspace/docs, /tmp/aurum-docs-output/../../etc,
/tmp/aurum-docs-output/ (barra final) e /tmp/aurum-docs-output-host
(irmão com o mesmo prefixo) são negados pelo mesmo motivo: nenhum deles é o
diretório limitado. A camada de integração exige adicionalmente que os três
roots sejam distintos entre si — um gerador que escreve na própria árvore de
fixtures, ou um output que sombreia o gerador pinado, tornaria mutável em tempo
de render aquilo que foi pinado por digest.
Comandos permitidos
["go", "test", "-mod=readonly", "./..."]
Qualquer outro comando, ou o mesmo comando com aridade diferente, invalida o plano. O ambiente é fechado sobre exatamente estas chaves:
| Variável | Valor |
|---|---|
GOPROXY |
off |
GOSUMDB |
off |
GONOSUMDB |
* |
GOTOOLCHAIN |
local |
CGO_ENABLED |
0 |
AURUM_DOCS_GENERATOR_ROOT |
/tmp/aurum-docs-tool |
AURUM_DOCS_FIXTURE_ROOT |
/tmp/aurum-docs-fixtures |
AURUM_DOCS_OUTPUT_ROOT |
/tmp/aurum-docs-output |
HTTP_PROXY |
(vazio) |
HTTPS_PROXY |
(vazio) |
NO_PROXY |
* |
As três variáveis AURUM_DOCS_* e os três roots declarados são duas grafias dos
mesmos diretórios; o teste de integração recusa qualquer divergência entre elas,
porque AURUM_DOCS_OUTPUT_ROOT é exatamente onde um arquivo renderizado
apareceria se apontasse para fora do tmpfs limitado. Nenhuma credencial, valor
de ambiente do host ou segredo entra no documento, e o loader recusa qualquer
valor com forma de credencial mesmo em campo cujo valor seja pinado por
constante.
Códigos de rejeição
O loader (ValidateRegistryAUR408 / ValidateProfileAUR408) é
side-effect-free, não abre socket, não renderiza documento, não cria subprocesso
e devolve exatamente um código:
| Código | Causa |
|---|---|
registry-missing |
registry ausente ou não regular |
schema-invalid |
registry, schema ou profile fora da forma canônica (inclui chave duplicada dentro de um objeto JSON e limite obrigatório ausente, como render_timeout_seconds) |
schema-missing |
documento de schema ausente |
schema-digest-mismatch |
schema_digest não reproduz os bytes do schema |
profile-missing |
documento do profile ausente |
profile-unregistered |
chave desconhecida, ou docs-tool-offline-v1 ausente do registry |
duplicate-profile |
a mesma chave publicada duas vezes |
order-invalid |
chaves fora da ordem canônica crescente |
credential-present |
valor de ambiente com forma de credential real |
digest-invalid |
digest vizinho fora do formato sha256:<64 hex>, ou gerador, renderer ou plugin set sem digest |
unsafe-plan |
limite obrigatório relaxado ou excedido, tmpfs ilimitado, corpus maior que o limite de entrada, output fora do tmpfs, site tool nativo, plugin dinâmico, fetch remoto, execução de snippet, allowlist de snippet ampliada, fixture remota, escrita no host, filesystem do host, subprocesso, output persistente, gerador cgo, rede, mount, socket, device, root ou capability |
lock-missing |
lock ausente |
mutable-input |
imagem por tag mutável, repositório inesperado ou lock fora do schema |
lock-digest-mismatch |
lock_digest não reproduz os bytes do lock |
As constantes de segurança do schema são pinadas duas vezes: nos bytes do
documento de schema e em profileSchemaConstants408 /
environmentSchemaConstants408, dentro de tests/unit/AUR-408.go. Relaxar o
schema e recomputar o schema_digest no registry move os dois arquivos de forma
consistente e ainda assim falha, porque o mapa em Go não se move com eles.
Mutantes provados
O acceptance executa cada mutante contra bytes reais em disco, na cópia descartável, e exige o código declarado; o restore volta byte-idêntico.
| Mutante | Mudança | Código |
|---|---|---|
| MUT-001 | chave docs-tool-offline-v1 duplicada com bytes diferentes |
duplicate-profile |
| MUT-002 | imagem do lock por tag mutável | mutable-input |
| MUT-003 | network de none para host |
unsafe-plan |
| mount habilitado | mounts para bind |
unsafe-plan |
| socket habilitado | sockets para unix |
unsafe-plan |
| device habilitado | devices para /dev/fuse |
unsafe-plan |
| root | user para 0:0 |
unsafe-plan |
| capability adicionada | cap_add para SYS_ADMIN |
unsafe-plan |
| plugin dinâmico | dynamic_plugin para allowed |
unsafe-plan |
| fetch remoto | remote_fetch para allowed |
unsafe-plan |
| snippet executável | snippet_execution para allowed |
unsafe-plan |
| allowlist ampliada | snippet_languages ganha "sh" |
unsafe-plan |
| site tool nativo | native_site_tool para pandoc |
unsafe-plan |
| gerador cgo | generator_cgo para true |
unsafe-plan |
| output no host | host_output para read-write |
unsafe-plan |
| filesystem do host | host_filesystem para read-only |
unsafe-plan |
| subprocesso | subprocess para allowed |
unsafe-plan |
| output persistente | output_mode para persistent |
unsafe-plan |
| output fora do tmpfs | output_root para /workspace/docs |
unsafe-plan |
| traversal do output | output_root para /tmp/aurum-docs-output/../../etc |
unsafe-plan |
| barra final | output_root para /tmp/aurum-docs-output/ |
unsafe-plan |
| irmão com mesmo prefixo | output_root para /tmp/aurum-docs-output-host |
unsafe-plan |
| fixture fora do tmpfs | fixture_root para /workspace/docs |
unsafe-plan |
| fixture remota | fixture_source para remote |
unsafe-plan |
| gerador fora do tmpfs | generator_root para /usr/local/bin |
unsafe-plan |
| output ilimitado | max_output_bytes para 0 |
unsafe-plan |
| output gigante | max_output_bytes para 34359738368 |
unsafe-plan |
| corpus gigante | max_documents para 128 |
unsafe-plan |
| tmpfs ilimitado | tmpfs_mb para 0 |
unsafe-plan |
| gerador sem digest | generator para unpinned |
digest-invalid |
| renderer sem digest | renderer para unpinned |
digest-invalid |
| plugin mutável | plugin_set para latest |
digest-invalid |
| limite ausente | render_timeout_seconds removido do documento |
schema-invalid |
| credential real | HTTP_PROXY para uma chave com forma real (montada em runtime) |
credential-present |
O vetor de credential nunca aparece como literal em arquivo versionado: ele é
montado em runtime, dentro da cópia descartável do acceptance, para que o portão
de credenciais de oci-run não veja forma de segredo em nenhuma entrada
materializada nem em nenhuma saída.
Loaders vizinhos
Registrar uma oitava chave fecha a allowlist dos loaders já entregues, então este card os estende no mesmo commit:
tests/unit/AUR-402.go— allowlist de chaves do registry canônico.tests/unit/AUR-403.go— allowlist, aridade (8) e ordem posicional.tests/unit/AUR-404.go— conjunto de chaves registradas eswitchdo loader.tests/unit/AUR-405.go— conjunto de chaves registradas eswitchdo loader.tests/unit/AUR-406.go— conjunto de chaves registradas eswitchdo loader.tests/unit/AUR-407.go— conjunto de chaves registradas eswitchdo loader.tests/contracts/AUR-404.go— aridade exata do registry (7para8).
Cada extensão é aditiva: nenhuma rejeição anterior desaparece, nenhuma igualdade vira desigualdade, nenhuma aridade exata vira mínimo e nenhuma chave sai de allowlist. Nenhum desses loaders lê os documentos da toolchain documental: cada um checa apenas os caminhos declarados e a forma do digest, de modo que registrar a ferramenta de documentação não pode re-apontar silenciosamente o plano Go unit, o plano Git, o plano do fake provider, o plano do parser worker nem o plano do state store.
A aridade total do registry é asserida em tests/unit/AUR-408.go, que este card
possui. tests/contracts/AUR-408.go não assere aridade: este card é dono da
chave docs-tool-offline-v1, não do tamanho do registry, e a próxima carta de
profile deve quebrar apenas onde sua posse estiver declarada.
Diagnóstico offline
# a chave está publicada e em ordem?
grep -n '"key"' .board/oci/profiles/registry.v1.json
# os digests do registry reproduzem os bytes?
sha256sum .board/schemas/docs-tool-offline-profile.schema.json \
.board/locks/oci/docs-tool-offline-v1.lock.json
printf '%s' "$(sed -n 's/.*"image": "\(.*\)".*/\1/p' \
.board/locks/oci/docs-tool-offline-v1.lock.json)" | sha256sum
# o runtime da imagem pinada ainda satisfaz o plano puro Go?
docker run --rm --pull=never --network=none --read-only \
--user=65534:65534 --cap-drop=ALL --entrypoint= \
aurum-bootstrap-go-bash@sha256:503ac356fa6bca4bad56fade87b5479ff371bd446ffb9c7db91f211323c7c73e \
bash -c 'command -v go; command -v pandoc || echo pandoc-absent; ls /sys/class/net'