Pular para conteúdo

AUR-533 — Artefatos de análise se atualizam sozinhos com idade máxima

Status

Implementado: internal/artifacts (manifesto, builder, cliente, política), internal/artifacts/cmd/analysis-data, scripts/artifacts/{build,publish}.sh, .github/workflows/analysis-data.yml. Prova: internal/artifacts/artifacts_test.go::TestAUR533*. Aceite: tests/acceptance/AUR-533.sh.

Ligação ao review e seção de config incluídas (card v2).

Artefato

Release GitHub analysis-data/<AAAAMMDDTHHMMSSZ> (timestamp UTC, sem colisão entre execuções manuais no mesmo dia). Assets planos, sem tarball: um osv-<ecossistema>.zip por ecossistema (a cópia all.zip oficial), o scanners.yml fixado e manifest.json. Decisão: sem tarball, porque os arquivos já são endereçáveis individualmente e o runtime baixa só o que usa; um tar duplicaria centenas de MB.

manifest.json (schema: aurum-analysis-data/v1): generated_at (RFC 3339, obrigatoriamente UTC), sources, files[] (path, sha256, size, kind, ecosystem), scanners (pares chave/valor de scanners.yml) e set_digest = sha256 das linhas ordenadas path NUL sha256 LF, que cobre nomes e conteúdos.

Ecossistemas (AC-005)

ecosystems.txt da fonte pública (osv-vulnerabilities.storage.googleapis.com) é lido a cada build; cada linha vira um download de <eco>/all.zip. Nenhuma lista no código; nome do arquivo é derivado por saneamento genérico. Um ecossistema que falha, um zip ilegível ou uma lista vazia abortam o build (sem artefato parcial).

Workflow (AC-001)

analysis-data.yml: schedule diário + workflow_dispatch. Job build roda build.sh no container Go fixado por digest (build, verify, go test com AURUM_ARTIFACT_DIR apontando para o artefato recém-gerado, só então grava dist.tests-passed). Job publish (needs: build, sem if/continue-on-error) roda publish.sh: recusa sem o marcador, reconfere cada arquivo contra o manifesto, cria o release como rascunho, sobe os assets e só então o publica; tag existente nunca é tocada. Falha em qualquer etapa deixa o release anterior como o mais novo. Actions fixadas por SHA de commit (checkout v4.2.2, upload-artifact v4.6.2, download-artifact v4.3.0; SHAs conferidos com gh api .../git/ref/tags/<tag>).

Runtime (AC-002, AC-003, AC-004)

artifacts.Resolve(ctx, Options) nunca retorna erro: toda falha é um Outcome{Usable:false, Reason, Detail}. Passos: lista releases com prefixo analysis-data/ (não rascunho, não pré-release), escolhe o de created_at mais novo, baixa e valida o manifesto (schema, caminhos planos, set_digest), aplica a idade ANTES de baixar o payload (vencido: analysis_data_stale, sem download), baixa/verifica cada arquivo por sha256 e tamanho (divergente: analysis_data_digest_mismatch). generated_at no futuro (> 10 min) é inválido. Sem rede: usa a cópia em cache mais nova, sujeita à mesma idade e reverificação; sem cópia: analysis_data_unavailable. Dentro da idade, Outcome.Audit() expõe analysis_data_digest, analysis_data_generated_at e analysis_data_tag. A API do GitHub é injetável (Options.APIBase); o token só vai para a API, nunca para o host dos assets.

Política e ligação ao review (card v2)

Tipo único em internal/config (analysisdata.go): Config.AnalysisData *AnalysisDataConfig (max_age_days 1..365, padrão 7 quando declarada sem valor; repository opcional). Ponteiro nil = não declarada (declare como mapeamento, analysis_data: {}; um analysis_data: nulo conta como não declarada). Validate roda em Parse; negativo ou acima de 365 é erro, nunca "sem limite". ApplyCentralPolicy governa a seção sozinha, como cada subseção de quality_gates: a política declara, vence e avisa; silenciosa, fica a do repositório. Isso também faz o decode estrito de LoadCentralPolicy aceitar a chave. internal/artifacts recebe valores prontos em Options (o antigo policy.go foi removido).

cmd/aurumcode/aur533.go::applyAnalysisDataGate, chamado em runReview (--base) e runPRReview (--pr) logo antes do gate Dependency-Track e integrado com mergeDTrackGate. Sem a seção: nenhuma chamada de rede, nenhuma linha nova. Outcome inutilizável: inconclusivo pelo gate.inconclusive (block falha o check; warn só retém a aprovação), com a linha analysis_data: revisão inconclusiva (<código>): <detalhe> e o código na razão da auditoria/SARIF. Outcome válido: nenhuma linha; a auditoria ganha analysis_data_digest, analysis_data_generated_at e analysis_data_tag. O review baixa apenas os arquivos de kind: scanners (Options.Kinds); a cópia OSV, grande e ainda sem consumidor no review, só é baixada e verificada por arquivo quando um consumidor a usar (AUR-495, via Options.Kinds); o manifesto inteiro continua coberto pelo set_digest, conferido em todo review.

A auditoria usa o campo tipado render.AuditRecord.AnalysisData (digest, generated_at, tag, omitempty), preenchido antes de WriteAuditRecord e redigido junto com o resto do registro. O caminho --pr usa a mesma função e o mesmo ponto de integração do --base, e tem seus próprios testes de ponta a ponta.

Cache e origem

Se a listagem de releases falha, Resolve usa a cópia em cache mais nova. Limite de confiança: o manifesto em cache só é validado contra si mesmo (set_digest, digest e tamanho de cada arquivo) e a idade é conferida; não há nova prova de autenticidade perante o GitHub. Outcome.Source (remote|cache) vai para AnalysisDataAudit.Source e, quando cache, para uma linha do parecer (sem tornar o resultado inconclusivo). max_age_days é *int: ausente = 7; 0, negativo ou > 365 explícitos = erro de carga.

Mutação

AC-003-MUT-002 remove a chamada do gate só do --pr (cópia) e deixa os testes --pr vermelhos. AC-003-MUT-001 neutraliza a única comparação de idade em client.go (cópia temporária); TestAUR533ResolveRefusesStaleArtifact fica vermelho.

Pendências

Retenção de releases antigos e "Immutable releases" são configuração do repositório publicador, não deste card.