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.