Tutorial: dados de análise (analysis_data)
Objetivo
O que a análise usa e envelhece (cópia da base pública OSV e as versões dos scanners fixadas pelo projeto) é publicado todo dia como um release do GitHub. A revisão só confia nesse artefato se ele existir, estiver dentro da idade máxima e tiver todos os digests íntegros; senão o resultado é inconclusivo, nunca aprovado. Ao final você terá visto, com o binário real do produto:
- que
analysis_datanão declarado não faz requisição nenhuma, e que declarado consulta, verifica e registra digest e data na auditoria; - um artefato vencido (
analysis_data_stale) comgate.inconclusive: blocke comwarn; - o cache: com a listagem fora do ar, a cópia verificada é usada e declarada como tal;
- o workflow agendado que publica (conferência estática do arquivo);
- quando falha: artefato adulterado (
analysis_data_digest_mismatch) e fonte indisponível (analysis_data_unavailable).
Cada saída abaixo vem de uma execução real, registrada em
demo/tutoriais/dados-de-analise/out/ e conferida por run.sh --check. Os
blocos de configuração são os arquivos de demo/tutoriais/dados-de-analise/,
byte a byte (tests/acceptance/AUR-563.sh compara).
Como a demonstração prova isso sem GitHub
O review consulta o endereço da API do GitHub que vem da variável
AURUMCODE_GITHUB_API_URL (padrão https://api.github.com; só https://, ou
http:// para um IP de loopback literal, como em teste). É a mesma variável
do cliente de PR. Para provar cada desfecho com o binário real, cada caso roda
um container da imagem do produto, sem rede (--network none), em que:
AURUMCODE_GITHUB_API_URL=http://127.0.0.1:8080aponta para o servidor local do próprio container: sem DNS, sem CA, sem TLS de demonstração;servidor-local.py(Python, só biblioteca padrão) atende como a API de releases, na porta 8080, servindo a lista, omanifest.jsone oscanners.yml, no modo pedido pelo caso;- ao final,
dentro.shlista as requisições que o servidor recebeu: é a prova de "zero rede" ou de qual caminho a revisão percorreu. O caso "sem rede" não define a variável: o padrão não tem rota no container.
Isso prova o comportamento do cliente do produto contra o formato do
GitHub que o código espera (o mesmo que o teste em Go
cmd/aurumcode/aur533_test.go usa). Não é o GitHub de verdade.
Pré-requisitos
git,docker,bashepython3no host. Nada mais.- A imagem do produto, construída do
Dockerfileda raiz:
docker build -t aurumcode:local /caminho/para/AurumCode
- O provedor de modelo é um arquivo determinístico (nenhuma credencial):
{"issues":[]}
Rodar a demonstração
bash demo/tutoriais/dados-de-analise/run.sh all # executa os seis casos e grava out/
bash demo/tutoriais/dados-de-analise/run.sh --check # compara out/ com expected/, sem docker
Cada caso cria um repositório descartável em
demo/tutoriais/dados-de-analise/.estado/ (ignorado pelo git): main com
app.go e a branch feature com uma mudança trivial e a configuração do
caso. Em uso real a configuração mora em .aurumcode/config.yml do seu
repositório (ou na política central, que decide sozinha se declarar a seção).
Os arquivos de configuração dos casos:
gate:
inconclusive: block
review:
language: pt-BR
analysis_data:
repository: owner/dados-de-analise
max_age_days: 7
gate:
inconclusive: block
review:
language: pt-BR
analysis_data:
repository: owner/dados-de-analise
max_age_days: 7
gate:
inconclusive: warn
review:
language: pt-BR
repository é opcional (o padrão é o repositório que publica o artefato do
AurumCode); max_age_days aceita de 1 a 365 (padrão 7). gate.inconclusive
decide o que um resultado inconclusivo faz: block reprova, warn só
impede a aprovação.
Caso 1: declarado ou não
Dois repositórios iguais, um sem analysis_data e outro com. O servidor local está no
ar nos dois, mas só o segundo o consulta. --auditoria grava o registro de
auditoria da execução.
aurumcode review --base main --auditoria audit.json
--- A. nao declarado (o servidor local esta no ar, mas ninguem o consulta)
$ aurumcode review --base main --auditoria audit.json
> **Aprovado: nenhum problema encontrado na mudança revisada.**
--- requisicoes recebidas pelo servidor local (modo valido): 0
exit_code=0
RESULTADO: sem analysis_data declarado o review nao fez nenhuma requisicao e nao imprimiu linha analysis_data
linhas citando analysis_data na saida do review: 0
auditoria: sem campo analysis_data
$ aurumcode review --base main --auditoria audit.json
> **Aprovado: nenhum problema encontrado na mudança revisada.**
--- requisicoes recebidas pelo servidor local (modo valido): 3
GET /repos/owner/dados-de-analise/releases -> 200
GET /dl/manifest.json -> 200
GET /dl/scanners.yml -> 200
exit_code=0
RESULTADO: artefato valido: o review aprovou e registrou digest e data na auditoria
auditoria analysis_data.source: remote
auditoria analysis_data.tag: analysis-data/<timestamp>
auditoria analysis_data.generated_at: <timestamp>
auditoria analysis_data.digest: sha256:339a66c28f655d6e784598fee6b2259858279bbd2a33b01f33a1d8ee162b19c1
O que observar:
- A (não declarado): 0 requisições, nenhuma linha citando
analysis_datae nenhum campoanalysis_datana auditoria: a seção ausente muda nada. - B (declarado): três requisições (listagem, manifesto,
scanners.yml), vereditoApprovee, na auditoria,source,tag,generated_ate odigestdo conjunto. Só os arquivoskind: scannerssão baixados num review; o digest do conjunto cobre os demais (a cópia OSV não é baixada). - As datas e o digest do bloco B variam por execução (a data do artefato é
"agora menos um dia"); o
--checkconfere só as partes estáveis. - As linhas
RESULTADO:são conclusões do script (exit esperado e, no caso A, contagem de requisições zero), não saída do produto.
Caso 2: artefato vencido
O servidor local serve um artefato gerado há 30 dias; o limite é max_age_days: 7.
Repare que a idade é conferida antes de baixar o scanners.yml: só duas
requisições.
aurumcode review --base main
--- gate.inconclusive: block
aurumcode review: policy gate: analysis_data: revisão inconclusiva (dados de análise vencidos [analysis_data_stale]): artifact analysis-data/<timestamp> generated <timestamp> is <duracao> days old, above max_age_days=7
> **Inconclusivo: esta revisão não aprova a mudança.**
--- requisicoes recebidas pelo servidor local (modo vencido): 2
GET /repos/owner/dados-de-analise/releases -> 200
GET /dl/manifest.json -> 200
exit_code=1
RESULTADO: artefato vencido com block: o review falha (analysis_data_stale)
--- gate.inconclusive: warn
aurumcode review: policy gate: analysis_data: revisão inconclusiva (dados de análise vencidos [analysis_data_stale]): artifact analysis-data/<timestamp> generated <timestamp> is <duracao> days old, above max_age_days=7
> **Inconclusivo: esta revisão não aprova a mudança.**
--- requisicoes recebidas pelo servidor local (modo vencido): 2
GET /repos/owner/dados-de-analise/releases -> 200
GET /dl/manifest.json -> 200
exit_code=0
RESULTADO: artefato vencido com warn: exit 0, mas a linha inconclusiva continua no parecer
O que observar:
analysis_data_stalecom a idade (30.0 dias) e o limite. Comblocko review sai 1; comwarnsai 0, mas o veredito éComment, nuncaApprove: o parecer segue sem a aprovação.- Para aceitar dados mais velhos, a organização muda
max_age_days(até 365); o repositório não afrouxa um limite que a política central declarou (isso é o caso 5 de política central).
Caso 3: cache
A primeira execução baixa e guarda a cópia em HOME/.cache/aurumcode/analysis-data
(a demonstração monta um diretório do host nesse lugar para que ele sobreviva
entre as execuções). Na segunda, a listagem de releases responde 503: a
cópia em cache é revalidada (manifesto contra si mesmo, digest de cada
arquivo, idade) e usada, e o uso fica explícito. Na terceira, sem nenhum cache
e com a listagem fora do ar, o resultado é inconclusivo.
aurumcode review --base main --auditoria audit.json
--- 1. servidor no ar: baixa, verifica e guarda no cache
aurumcode review: reaproveitamento do veredito do gate indisponível (AURUMCODE_CACHE_DIR não definido): o veredito desta execução não pode ser compartilhado com outra execução, nem reaproveitar um anterior
--- requisicoes recebidas pelo servidor local (modo valido): 3
GET /repos/owner/dados-de-analise/releases -> 200
GET /dl/manifest.json -> 200
GET /dl/scanners.yml -> 200
exit_code=0
RESULTADO: primeira execucao: artefato remoto verificado e guardado no cache
auditoria analysis_data.source: remote
--- 2. a listagem de releases cai (HTTP 503); a copia em cache, ainda dentro da idade, e usada
aurumcode review: reaproveitamento do veredito do gate indisponível (AURUMCODE_CACHE_DIR não definido): o veredito desta execução não pode ser compartilhado com outra execução, nem reaproveitar um anterior
aurumcode review: policy gate: analysis_data: usando cópia em cache (analysis-data/<timestamp>): a listagem de releases estava indisponível; idade e digests verificados
--- requisicoes recebidas pelo servidor local (modo indisponivel): 1
GET /repos/owner/dados-de-analise/releases -> 503
exit_code=0
RESULTADO: listagem fora do ar com copia valida em cache: usa o cache e o declara
auditoria analysis_data.source: cache
--- 3. sem cache nenhum e a listagem fora do ar: inconclusivo
aurumcode review: reaproveitamento do veredito do gate indisponível (AURUMCODE_CACHE_DIR não definido): o veredito desta execução não pode ser compartilhado com outra execução, nem reaproveitar um anterior
aurumcode review: policy gate: analysis_data: revisão inconclusiva (dados de análise indisponíveis [analysis_data_unavailable]): GET http://127.0.0.1:8080/repos/owner/dados-de-analise/releases?[REDACTED] HTTP 503
--- requisicoes recebidas pelo servidor local (modo indisponivel): 1
GET /repos/owner/dados-de-analise/releases -> 503
exit_code=1
RESULTADO: sem copia em cache e sem listagem: analysis_data_unavailable
O que observar:
- A segunda execução faz uma requisição (a listagem, 503) e passa: a linha
do parecer diz
usando cópia em cachee a auditoria registrasource: cache(na primeira eraremote). Não é falha, mas também não é silencioso. - A cópia em cache prova integridade e frescor, não autenticidade perante o GitHub (a documentação diz o mesmo).
- Sem cache e sem listagem:
analysis_data_unavailable, exit 1 comblock. - Não demonstrado aqui: o cache num runner de CI. Um runner efêmero começa sem cache, então lá o 503 do terceiro passo é o caminho comum, a menos que o diretório de cache seja persistido pelo seu pipeline.
Caso 4: o workflow agendado que publica
O artefato nasce de .github/workflows/analysis-data.yml, que roda todo dia,
reconstrói e testa o artefato e só então publica um release de tag
analysis-data/<AAAAMMDDTHHMMSSZ>. Este caso não executa o workflow (ele
só existe no GitHub Actions): confere, linha a linha, que o arquivo tem o que
este texto afirma. O arquivo abaixo é uma cópia do original; o caso falha se
os dois divergirem.
name: Analysis data artifact
# AUR-533: everything the analysis uses that ages and is not a Go dependency
# (the public OSV database, the pinned scanner versions) is rebuilt on a
# schedule, tested, and only then published as an immutable GitHub Release
# tagged analysis-data/<UTC timestamp>. A failing build or test leaves the
# previous release as the newest one. The runtime refuses data older than
# analysis_data.max_age_days (default 7).
# AUR-618: each copy is published as a pre-release (the product's releases
# keep "Latest") and, right after publishing, the copies older than the
# retention window are deleted, release and tag, keeping at least the newest
# ones. Both numbers are inputs of the manual run; the schedule uses the
# defaults, which match what the runtime accepts.
on:
schedule:
- cron: '17 3 * * *'
workflow_dispatch:
inputs:
retention_days:
description: 'Delete analysis-data copies published more than this many days ago'
type: string
default: '7'
keep_at_least:
description: 'Never delete the newest N analysis-data copies'
type: string
default: '3'
concurrency:
group: analysis-data
cancel-in-progress: false
permissions:
contents: read
jobs:
build:
name: Build and test the artifact
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Build and test with the project toolchain
run: >-
docker run --rm
-v "${GITHUB_WORKSPACE}:/src"
-w /src
golang:1.27.1-alpine3.24@sha256:8a5910f31396cd4d89662f56c68b3ae31d374308270a1c3bd96672ee5ed43414
sh -c 'apk add --no-cache bash >/dev/null && bash scripts/artifacts/build.sh dist'
- name: Upload the tested artifact for the publish job
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: analysis-data
path: |
dist
dist.tests-passed
if-no-files-found: error
retention-days: 1
publish:
name: Publish the tested artifact
needs: build
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Download the tested artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: analysis-data
- name: Publish immutable release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: bash scripts/artifacts/publish.sh dist
- name: Delete the copies the runtime no longer accepts
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RETENTION_DAYS: ${{ inputs.retention_days || '7' }}
KEEP_AT_LEAST: ${{ inputs.keep_at_least || '3' }}
run: bash scripts/artifacts/retain.sh --days "$RETENTION_DAYS" --keep "$KEEP_AT_LEAST"
NAO EXECUTADO: o workflow roda so no GitHub Actions; aqui so a conferencia estatica do arquivo.
workflow-publicador.yml: identico a .github/workflows/analysis-data.yml
gatilho: schedule diario (cron 17 3 * * *)
gatilho: workflow_dispatch (disparo manual)
publish depende de build: teste falhando nao publica
so o job publish recebe contents: write
publish.sh: so aceita tag analysis-data/*
publish.sh: cria como rascunho e so depois publica (nunca fica visivel pela metade)
publish.sh: tag existente nunca e tocada
RESULTADO: o workflow tem o agendamento, o teste antes de publicar e a publicacao em duas etapas que o texto descreve (conferencia estatica; a imutabilidade do release depende de 'Immutable releases' no repositorio e NAO foi demonstrada)
O que observar:
schedulediário eworkflow_dispatch; o jobpublishdepende debuild(teste falhando mantém o release anterior como o mais novo) e só ele temcontents: write.scripts/artifacts/publish.shcria o release como rascunho e só depois o publica, e nunca toca numa tag existente.- Não demonstrado: que o release seja imutável. Isso depende de ativar "Immutable releases" nas configurações do repositório publicador; o script não consegue verificar isso.
Quando falha
Artefato adulterado
Dois modos: o scanners.yml servido não bate com o digest do manifesto, ou o
set_digest do manifesto não bate com a lista de arquivos.
--- A. scanners.yml servido diferente do digest do manifesto
aurumcode review: policy gate: analysis_data: revisão inconclusiva (os dados de análise não conferem com o artefato declarado [analysis_data_digest_mismatch]): scanners.yml: expected sha256:fd430ae5426ecd17bf13c1eeb8921762667931bf0b22e388118766633420c393 (29 bytes), got sha256:2b58c9cd04047f8c9951f562d7926a8e13b01775ce2469bdecbec1be8b801fba (28 bytes): digest mismatch
--- requisicoes recebidas pelo servidor local (modo adulterado-arquivo): 3
GET /repos/owner/dados-de-analise/releases -> 200
GET /dl/manifest.json -> 200
GET /dl/scanners.yml -> 200
exit_code=1
RESULTADO: arquivo adulterado: analysis_data_digest_mismatch reprova
--- B. set_digest do manifesto nao confere com a lista de arquivos
aurumcode review: policy gate: analysis_data: revisão inconclusiva (os dados de análise não conferem com o artefato declarado [analysis_data_digest_mismatch]): manifest: set_digest "sha256:0000000000000000000000000000000000000000000000000000000000000000" does not match its files (sha256:339a66c28f655d6e784598fee6b2259858279bbd2a33b01f33a1d8ee162b19c1): digest mismatch
--- requisicoes recebidas pelo servidor local (modo adulterado-manifesto): 2
GET /repos/owner/dados-de-analise/releases -> 200
GET /dl/manifest.json -> 200
exit_code=1
RESULTADO: manifesto adulterado: analysis_data_digest_mismatch reprova
O que observar: nos dois modos, analysis_data_digest_mismatch e exit 1. No
modo do arquivo, o conteúdo foi baixado e rejeitado (três requisições); no do
manifesto, a revisão parou antes de baixar o arquivo (duas).
Fonte indisponível
A listagem responde 503 sem cache; depois, sem rede nenhuma (sem servidor local, o
container não tem rota); e o mesmo com gate.inconclusive: warn.
--- A. a listagem responde HTTP 503 e nao ha cache
aurumcode review: reaproveitamento do veredito do gate indisponível (AURUMCODE_CACHE_DIR não definido): o veredito desta execução não pode ser compartilhado com outra execução, nem reaproveitar um anterior
aurumcode review: policy gate: analysis_data: revisão inconclusiva (dados de análise indisponíveis [analysis_data_unavailable]): GET http://127.0.0.1:8080/repos/owner/dados-de-analise/releases?[REDACTED] HTTP 503
--- requisicoes recebidas pelo servidor local (modo indisponivel): 1
GET /repos/owner/dados-de-analise/releases -> 503
exit_code=1
RESULTADO: listagem 503 sem cache: analysis_data_unavailable reprova
--- B. sem rede nenhuma (container sem rota, nada escuta)
aurumcode review: reaproveitamento do veredito do gate indisponível (AURUMCODE_CACHE_DIR não definido): o veredito desta execução não pode ser compartilhado com outra execução, nem reaproveitar um anterior
aurumcode review: policy gate: analysis_data: revisão inconclusiva (dados de análise indisponíveis [analysis_data_unavailable]): <detalhe de rede omitido do registro>
exit_code=1
RESULTADO: sem rede e sem cache: analysis_data_unavailable reprova
--- C. o mesmo com gate.inconclusive: warn
aurumcode review: reaproveitamento do veredito do gate indisponível (AURUMCODE_CACHE_DIR não definido): o veredito desta execução não pode ser compartilhado com outra execução, nem reaproveitar um anterior
aurumcode review: policy gate: analysis_data: revisão inconclusiva (dados de análise indisponíveis [analysis_data_unavailable]): <detalhe de rede omitido do registro>
exit_code=0
RESULTADO: sem rede com warn: exit 0, mas nunca aprovado silenciosamente
O que observar:
analysis_data_unavailablecomblocksai 1; comwarn, sai 0 e a linha inconclusiva continua no parecer.- O detalhe do erro de rede (B e C) varia por ambiente e é trocado por
<detalhe de rede omitido do registro>pelo filtroTUT_SEDdorun.sh, declarado ali. No caso do 503 o detalhe é mostrado e vem com?[REDACTED]no lugar da consultaper_page=100: o filtro de segredos do produto mascara a parte depois do?da URL (cosmético; ver "Achados"). - Quarto desfecho do código,
analysis_data_invalid(manifesto ilegível ou com esquema desconhecido,max_age_daysinválido), não tem caso executado aqui.
Problemas comuns
analysis_data_unavailableem CI sem rede para o GitHub: oreviewconsulta o endereço público da API do GitHub e não há opção para trocá-lo. Em runner isolado, libere esse destino ou aceite o resultado inconclusivo.analysis_data_stalelogo depois de um feriado: o publicador falhou ou não rodou; veja o histórico do workflow. Subirmax_age_dayssó esconde o atraso.- Esperar que
warnaprove:warnnão reprova, mas o veredito viraComment; só um artefato íntegro e dentro da idade deixa passar a aprovação. - Repositório privado: o token vem de
GITHUB_TOKEN; sem ele só repositórios públicos são lidos (não demonstrado aqui). - O repositório quer outro limite: sob política central que declara
analysis_data, a seção do repositório é ignorada com aviso.
Achados sobre o produto
- O endereço da API do GitHub do
analysis_datavem deAURUMCODE_GITHUB_API_URL(AUR-571). O tutorial media, antes, que o endereço era fixo no binário e precisava de um servidor HTTPS falso com certificado próprio. - O filtro de segredos mascara a consulta da URL de listagem
(
releases?[REDACTED]) nas mensagens de erro. - A cópia em cache usada com a listagem fora do ar não é prova de autenticidade, só de integridade e frescor.
Como fica
Capturas geradas por scripts/docs/capturas.sh a partir das saídas gravadas em demo/tutoriais/dados-de-analise/out/: o terminal de cada caso e, quando o caso publica, o comentario do PR e os status checks. O manifesto docs/assets/capturas/capturas.json registra o digest de cada insumo.
adulterado


cache


declarado-ou-nao


indisponivel


vencido


workflow-agendado
