Pular para conteúdo

AUR-534: Toolchain Go suportado e atualizado por automacao

Documento historico do board. Para uso atual, consulte o guia do produto.

second-reader-go-v1 / go-git-offline-v1: legado congelado, nao um bloqueio atual

.board/bin/second-reader usa .board/locks/oci/second-reader-go-v1.lock.json (golang@sha256:2414035b..., go1.21.13, GOTOOLCHAIN=local forcado) e go-git-offline-v1 pina golang@sha256:4746d264... (tambem go1.21.13). Nenhum dos dois foi trocado por este card -- a instrucao reserva essa troca ao coordenador, e ambos pinam uma imagem golang pura, nao a aurum-bootstrap-go-bash deste card. Confirmado por execucao direta: um go build contra um go.mod com go 1.27 dentro de qualquer uma das duas falha com go: go.mod requires go >= 1.27 (running go 1.21.13; GOTOOLCHAIN=local).

Essa execucao so importa, porem, onde esses dois locks sao de fato chamados: .board/validate.sh (unico lugar que invoca bin/second-reader) e .board/bin/second-reader em si. Por .board/README.md ("## Operating model", 2026-08): "O processo atual nunca invoca .board/validate.sh; esse script e sua ceremonia de second-reader sao evidencia legada congelada, nao um portao de sessao." Nenhum card em backlog/ready/doing/review/validating/blocked-on-owner declara go-git-offline-v1, e o second-reader so roda via validate.sh, que o processo atual nao chama. Nao e, portanto, um bloqueador de integracao para o fluxo leve atual -- e um gap real para o dia em que validate.sh/second-reader seja reativado (ou para uma evidencia done anterior re-verificada com --verify), e por isso fica registrado aqui, mas nao impede este card de integrar nem bloqueia revisao de nenhum card Go corrente.

O que mudou

go.mod passa de go 1.21 para go 1.27. Go 1.21 saiu de suporte em 2024; uma ferramenta de compliance nao pode rodar sobre uma toolchain sem correcoes de seguranca. Go 1.27 e a versao estavel atual na familia Go 1.2x (1.26 perde suporte antes da proxima janela de manutencao deste board).

Nenhuma dependencia muda. go.sum e byte-idêntico antes e depois do salto (sha256:b9142883d8f8e46998f705fbcd9964d6f386e17381ddf95a0c3173c832ce25e8); so a diretiva go em go.mod muda.

Imagem do toolchain

Nova imagem, de Dockerfile versionado no repositorio em .board/oci/images/go-bash-1.27/Dockerfile:

FROM golang:1.27.1-alpine3.24@sha256:8a5910f31396cd4d89662f56c68b3ae31d374308270a1c3bd96672ee5ed43414
RUN apk add --no-cache bash
WORKDIR /opt/aurum-a006
COPY go.mod go.sum ./
RUN GOTOOLCHAIN=local GOPROXY=https://proxy.golang.org GOSUMDB=sum.golang.org go mod download
COPY .board/oci/images/go-bash-1.27/Dockerfile /opt/aurum-a006/toolchain.Dockerfile
ENV GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off GONOSUMDB=*

Mesmo padrao de bootstrap-readonly-v1/Dockerfile: base pinada por digest, go.mod/go.sum reais do repositorio copiados no build (modulo cache assado na imagem, ja que a sealed run roda offline). Duas diferencas deliberadas:

  • Sem ENTRYPOINT ["/bin/bash"]. go-sealed, go-shared e go-live chamam a imagem com um comando explicito (go ... / sleep infinity / ...) sem --entrypoint=; herdar o entrypoint de bash do bootstrap-readonly-v1 faria cada chamada virar /bin/bash go ... e morrer. oci-run zera o entrypoint por conta propria, entao nao precisa de nenhum dos dois.
  • A propria Dockerfile e copiada para dentro da imagem (/opt/aurum-a006/toolchain.Dockerfile), junto com o go.mod/go.sum assados. E a base da prova de AC-004: a suite roda dentro da imagem sem Docker disponivel, entao o unico jeito de provar "esta imagem nasceu deste Dockerfile exato, com este go.mod/go.sum exatos" de dentro do sandbox e comparar byte a byte o que foi assado contra o que foi materializado.

Build (fora de qualquer card, para reconstruir a imagem apos uma mudanca):

docker build -f .board/oci/images/go-bash-1.27/Dockerfile \
  -t aurum-bootstrap-go-bash:1.27.1-<data> .
docker image inspect -f '{{.Id}}' aurum-bootstrap-go-bash:1.27.1-<data>

O .Id resultante (neste Docker 29, com o containerd image store, igual ao unico RepoDigests da imagem recem-construida) e o digest que entra em cada lock como aurum-bootstrap-go-bash@sha256:<id>. Confirmado para esta troca:

sha256:99bbed33cc0ac22f6fe947f589b82930dff841c35e6f8817f9c9fbae2adedb40

Esse .Id nao e bit-reproduzivel entre rebuilds. apk add --no-cache bash resolve a versao mais recente de bash (e das libs Alpine que ela traz) no momento do build, sem pin de versao -- um rebuild dias depois, com um pacote bash novo no repositorio Alpine, produz um .Id diferente mesmo com o mesmo Dockerfile e o mesmo go.mod/go.sum. O que e estavel entre rebuilds e o CONTEUDO funcional (base Go pinada por digest, bash presente, modulo cache baixado dos mesmos go.mod/go.sum), nao o byte-a-byte da imagem. Por isso o digest do lock precisa ser recalculado a cada rebuild (a cadeia de digest acima existe exatamente para isso), e por isso nenhuma automacao pode assumir que o .Id de hoje continua valido amanha so porque o Dockerfile nao mudou.

Smoke test (replica o probe do oci-run):

docker run --rm --pull=never --network=none --read-only \
  --user 65534:65534 --cap-drop=ALL \
  --tmpfs /tmp:rw,nosuid,nodev,size=128m --entrypoint= \
  aurum-bootstrap-go-bash@sha256:99bbed33cc0ac22f6fe947f589b82930dff841c35e6f8817f9c9fbae2adedb40 \
  bash -c 'command -v bash && go version'

Cadeia de digest

oci-run nunca le registry.v1.json; o unico caminho que vale e .board/oci/profiles/<nome>.json + .board/locks/oci/<nome>.lock.json. A troca:

  1. image em cada lock *.lock.json passa a aurum-bootstrap-go-bash@sha256:99bbed33cc0ac22f6fe947f589b82930dff841c35e6f8817f9c9fbae2adedb40.
  2. lock_digest em cada profile .json (o campo que oci-run de fato valida contra sha256sum do arquivo de lock) e recalculado do lock novo.
  3. .board/oci/profiles/registry.v1.json (registro secundario, nao lido por nenhum script encontrado neste pass -- so documental) tem lock_digest e image_set_digest recalculados por entrada, usando o campo lock de cada entrada, nao o nome da chave. Isso importa porque a entrada bootstrap-readonly-v1 desse arquivo aponta lock para registry-v1.lock.json, nao para bootstrap-readonly-v1.lock.json -- uma inconsistencia que ja existia antes deste card e que este card nao corrige, so preserva corretamente (o lock_digest de uma entrada tem que bater com o arquivo que a propria entrada nomeia, nao com o arquivo cujo nome parece corresponder).
  4. image_set_digest = sha256(printf '%s' "aurum-bootstrap-go-bash@sha256:<digest>"), sem newline final -- formula confirmada reproduzindo o valor atual (c12ef435...) contra a imagem antiga antes de escrever qualquer valor novo.

Locks trocados (10, todos os que pinavam aurum-bootstrap-go-bash@sha256:e8521445a6c1bc7c017258e9c71caf8eb78ee254862582c5d57d8a79d9413084): bootstrap-readonly-v1, docs-tool-offline-v1, fake-provider-v1, fake-scm-offline-v1, go-unit-offline-v1, oci-conformance-v1, parser-worker-v1, polyglot-toolchain-v1, registry-v1, sqlite-offline-v1. (O briefing inicial deste card citava 12; a contagem real na arvore e 10 -- recomputada, nao assumida.)

Locks nao trocados, por pinarem uma imagem golang diferente, nao a aurum-bootstrap-go-bash: go-git-offline-v1 (golang@sha256:4746d26432a9117a5f58e95cb9f954ddf0de128e9d5816886514199316e4a2fb) e second-reader-go-v1 (golang@sha256:2414035b086e3c42b99654c8b26e6f5b1b1598080d65fd03c7f499552ff4dc94). Nenhum card em backlog/ready/doing/review/validating/blocked-on-owner declara esses dois profiles hoje (so done/cancelled), entao a troca nao bloqueia nenhum trabalho em andamento.

.board/bootstrap/locks/go.yml e .board/bootstrap/locks.yml

go_version passa a 1.27; go_mod_sha256 e recalculado do go.mod novo (go_sum_sha256 fica igual, porque go.sum nao mudou). O identity em locks.yml e recalculado pela formula exata de .board/bootstrap/verify.sh (sha256 das linhas AUR-xxx sha256:<digest> de cada um dos 6 filhos, em ordem lexicografica, com newline final) -- reproduzida primeiro contra o valor atual antes de ser confiada, e depois confirmada rodando bash .board/bootstrap/verify.sh .board/bootstrap, que retorna ok com o identity novo.

Automacao (AC-003)

.github/dependabot.yml cobre tres ecossistemas, todos semanais: gomod (raiz), docker (raiz e .board/oci/images/go-bash-1.27, a nova imagem) e github-actions. Limite conhecido: o Dependabot nao interpreta imagem Docker dentro de uma linha run: de workflow, nem dentro de um Makefile, nem uma variavel (GO_IMAGE ?= ...) -- ele so atualiza um FROM em Dockerfile e a referencia de uma uses: action. As duas linhas docker run ... golang:1.27.1-... em .github/workflows/ci.yml e as tres referencias de imagem em Makefile (GO_IMAGE default e o golang:1.27.1- bookworm fixo do target test-race) continuam precisando de edicao manual quando a proxima versao sair. Isso nao bloqueia AC-003 (que pede cobertura dos tres ecossistemas, nao automacao total de toda referencia de imagem), mas e um limite real, nao "CI totalmente automatizado".

Terceiro limite: tests/acceptance/AUR-534.sh tambem fixa 1.27.1 e os dois digests de base (alpine3.24, bookworm) como literais no proprio texto do script (AC-001 e AC-004). Um bump futuro para 1.27.2 (ou 1.28.x) precisa editar esses literais tambem -- nao e so go.mod + Dockerfile + Makefile + ci.yml + os locks, e mais um lugar na lista de "o que esta automacao nao fecha sozinha".

Segundo limite, mais importante: mesmo o FROM que o Dependabot consegue editar (.board/oci/images/go-bash-1.27/Dockerfile) nao fecha o ciclo por si so. Um PR do Dependabot muda o digest base; ele nao builda a imagem, nao calcula o novo aurum-bootstrap-go-bash@sha256:..., e nao atualiza os 10 locks, os 10 lock_digest de profile, nem o registry.v1.json. Esse e exatamente o trabalho manual que a secao "Cadeia de digest" acima descreve -- docker build + docker image inspect -f '{{.Id}}' + reescrever cada lock/profile/registry com o digest novo. "Atualizado por automacao" (o titulo deste card) vale para a DETECCAO de uma versao nova disponivel, nao para o fechamento de toda a cadeia de digest sem intervencao humana.

Referencias historicas fora do alcance deste card (nao editadas)

Achadas por grep dos digests antigos fora de .board/locks/oci, .board/oci/profiles e .board/bootstrap (os unicos caminhos que este card possui). Nao sao paths deste card; ficam estaticas, como prova historica do que rodou no passado, nao como afirmacao do estado atual:

  • .board/evidence/** (muitos cards done): evidencia congelada de execucoes passadas contra a imagem antiga. forbidden_paths deste card proibe tocar .board/evidence; correto nao editar.
  • .board/research/legacy-files.tsv: inventario historico do AUR-001, registra o digest antigo de go.mod e do go.yml antigo.
  • docs/specs/AUR-457.md, docs/specs/AUR-360.md: specs de cards done que citam o digest antigo da imagem/go.mod como registro do que foi executado na epoca.
  • tests/bootstrap/locks/AUR-360/** (fixtures e cases.tsv): fixtures autocontidas do validador de lock do AUR-360, com copias proprias de go.yml e seus proprios digests esperados -- nao derivam do go.yml real e continuam internamente consistentes sem mudanca.
  • tests/e2e/AUR-486.sh, tests/acceptance/AUR-457.sh: usam go 1.21 como conteudo de scratch (um go.mod sintetico descartavel criado dentro do proprio teste), nao como declaracao da toolchain do projeto.

Rollback

Reverter go.mod e as imagens (Dockerfile raiz, Dockerfile da nova imagem, os 10 locks, os profiles, registry.v1.json, go.yml, locks.yml) para o estado anterior a este commit. Nenhum dado de usuario envolvido.

Validacao

  • go vet ./..., go build ./... e go test -count=1 ./... rodados via go-sealed com GO_SEALED_IMAGE=aurum-bootstrap-go-bash:1.27.1-2026-10-02 e GO_SEALED_MODCACHE=/nonexistent (para nao herdar o modcache do host e provar que o modcache assado na imagem basta): go vet limpo, go test passa nos 35 pacotes da arvore completa, exit 0.
  • go mod tidy -diff, no mesmo ambiente, falha por dois motivos pre-existentes e fora do escopo deste card: tests/contracts/AUR-309.go (atras de //go:build aur309_characterization, inativa por padrao) importa um pacote que nunca existiu (tests/characterization/legacy/pipeline); e gopkg.in/check.v1 (usado so pelos testes internos do gopkg.in/yaml.v3, nunca pelo codigo deste repositorio) nao esta no modulo cache assado e GOPROXY=off impede buscar. Os dois ja existiam identicos em main antes deste card (confirmado via git show main:...); nenhum dos dois e causado pelo salto de versao, e corrigi-los excede "o que o salto exige" (Non-goals).
  • .board/tests/go-profile-regression.sh (prova real do AUR-508 para o profile go-unit-offline-v1, independente deste card) roda contra o profile e o lock ja trocados: real Go PASS, mutated Go RED, lock/privilege/runtime rejected, exit 0.
  • tests/acceptance/AUR-534.sh all, rodado diretamente contra a imagem nova com as MESMAS flags de container que o oci-run usa, mas com a worktree inteira montada (nao so os paths/read_paths deste card): AUR-534/all/pass, exit 0. Cada selector roda isolado com exit 0, AC-002 inclusive (go test ./... passa nos 35 pacotes); a mutacao adversarial de AC-001-MUT-001 testa DUAS mutacoes independentes sobre uma copia limpa de cada entrada que check_ac001 le -- reintroduzir 1.21 so em ci.yml (Makefile limpo) e, separadamente, so em Makefile (ci.yml limpo) -- e exige que check_ac001, a funcao real do gate, caia nas duas, nao so que o helper interno de scan "funcione" em isolamento. Isso prova que Makefile (adicionado aos scan_targets e aos checks de pin de golang:1.27.1-alpine3.24/bookworm por digest nesta revisao) esta realmente coberto por AC-001, nao so citado.
  • O accept real do card -- ./.board/bin/oci-run --profile go-unit-offline-v1 --card AUR-534 -- retorna exit 0. A versao 4 do card (mesclada de main neste pass) ampliou read_paths para [cmd, internal, pkg, action.yml, scripts, .board/bin, tests, demo, standards, docs/specs, .board/schemas, .board/research, .board/decisions], cobrindo tests/fixtures/repos/git-demo/repo.git, tests/integration e .board/schemas/task-spec.schema.json -- os tres caminhos externos que a suite completa precisa e que a v3 do card nao possuia nem lia. Com isso, dentro do proprio sandbox do oci-run, os 35 pacotes de go test ./... passam de fato (nao um subconjunto), AC-002 inclusive. check_ac002 nao hardcoda mais esses tres caminhos como guarda de infra: generaliza para qualquer falha de resolucao de pacote em go build ./... (mensagem de "cannot find package" ou equivalente) virar infra() em vez de vermelho comportamental, sem depender de uma lista fixa que pode ficar obsoleta a proxima vez que read_paths mudar.