@fidera/collector
v0.2.0
Published
O coletor forge-native: lê `git` e a forja **na borda do adotante** e empurra os primitivos crus para o Fidera. Zero dependência de runtime — roda via `npx` no CI do adotante.
Downloads
257
Readme
@fidera/collector
O coletor forge-native: lê git e a forja na borda do adotante e empurra os primitivos crus
para o Fidera. Zero dependência de runtime — roda via npx no CI do adotante.
Quickstart
O escopo
@fiderano npm é da organizaçãofidera(reivindicado em 19/08/2026 pelo governador — antes disso o 404 deixava qualquer terceiro publicá-lo e ser executado no runner do adotante, com oGITHUB_TOKENdele em mãos; a razão da versão exata abaixo é a mesma).
permissions:
contents: read # o checkout, e o `git log`/`git diff` locais
actions: read # `/actions/runs` — o eixo de CI
pull-requests: read # `/commits/{sha}/pulls` e os comentários do portão
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 200 # 0 apenas para `--mode all`; ver "Modos"
- run: npx --yes @fidera/[email protected] --mode tail --fidera-url https://api.fidera.app --tenant SEU-SLUG
env:
GITHUB_TOKEN: ${{ github.token }}
FIDERA_INGEST_TOKEN: ${{ secrets.FIDERA_INGEST_TOKEN }}⚠️ O bloco permissions: não é opcional. Com o default restrito de GITHUB_TOKEN — o padrão de
organizações novas — o token recebe contents: read e nada mais, e aí /actions/runs,
/commits/{sha}/pulls e /issues/{n}/comments devolvem 403. Esse 403 não traz
x-ratelimit-remaining: 0, então não é limite de taxa: é permissão. O coletor passou a dizer isso
na mensagem de erro, mas evitar o erro é melhor do que diagnosticá-lo.
⚠️ A versão é exata, e não é preciosismo. Um range (@1, @latest) resolve no runner do
adotante, num processo que ninguém está olhando, com o GITHUB_TOKEN do repositório em mãos.
Versão exata é o que faz o adotante decidir quando o código que ele executa muda. A guarda
fitness/quickstart-versao-exata.test.ts recusa range e exige que este número seja o do
package.json.
--yes é da mesma família: sem ele, npx num pacote ainda não baixado abre um prompt, e num
runner sem tty isso é um step pendurado até o timeout — com um diagnóstico que não aponta para cá.
Modos
| --mode | o que faz | fetch-depth |
|---|---|---|
| tail (padrão) | re-observa a cauda de merges. É a corrida diária. | 200 |
| all | backfill: todos os merges, mais a varredura de drift na forja. | 0 |
| drift | só a observação de drift deste run, lida do workspace. Zero git, zero forja. | 200 |
A cauda é re-observada de propósito: re-run de CI e edição do comentário do portão acontecem
depois de o merge assentar, e o servidor faz upsert por (tenant, mergeSha, collectorVersion).
--mode drift devolve merges: [] por desenho — uma corrida de PR não pode reportar como fato o
que ainda não foi integrado.
Contrato de entrada
| flag | obrigatória | padrão |
|---|---|---|
| --fidera-url | sim | — |
| --tenant | sim | — |
| --mode | não | tail |
| --main | não | main |
| --tail-k | não | 5 |
| --lookback-days | não | 90 |
| --repo-root | não | . |
| --observacao | não | build/maturity/drift-observacao.json |
| --dry-run | não | false |
| variável | para quê |
|---|---|
| FIDERA_INGEST_TOKEN | obrigatória — autoriza o POST. Tenant-scoped, escopo de ingestão. |
| GITHUB_TOKEN | leitura da forja. github.token basta. |
| GITHUB_REPOSITORY | obrigatória — o runner já a publica. |
Tudo isso é validado na entrada, antes de qualquer I/O: um --tenant esquecido falha em
milissegundos, e não depois de gastar o orçamento de requisições da forja.
Canais
- stdout — com
--dry-run, o envelopecollector-v1, um documento JSON parseável. É contrato. - stderr — o resumo do run real (lotes e contagem por status, emitido mesmo no sucesso), além de avisos, perdas contadas e recusas do servidor. stderr não-vazio não indica falha; o sinal de falha é o código de saída.
Nenhum canal ecoa o FIDERA_INGEST_TOKEN: o log de um run é público em repositório aberto.
O que "sucesso" significa aqui
O código de saída é 0 só quando o POST foi aceito e o relatório por item não trouxe
invalid. Um 200 cujo corpo diz que todas as observações foram recusadas — tenant divergente do
token é o caso real — não é sucesso, e sai 1.
Quando nada pôde ser lido, o resumo sai como não verificado, nunca como zero: "não li" não
pode ser confundido com "zero inválidos". A completude é medida por item — um relatório com
menos itens do que o lote enviado não passa por completo, senão o run afirmaria {"invalid":0}
sobre observações de que nada sabe.
Numa carga multi-lote a leitura pode ser parcial: um lote responde relatório legível, outro
não. O que foi apurado é piso confirmado, não estimativa — uma recusa contada num lote sai 1
mesmo que o relatório de outro lote nunca tenha chegado, e o resumo sai como
ao menos {…} (relatório incompleto). O que falta é o total, nunca o piso. O contrário —
absolver o run porque um lote veio ilegível — era o furo da issue #47.
Segue daí uma assimetria que vale conhecer: com uma carga toda inválida, o run sai 1 se a recusa
cair num lote lido, e 0 se o único lote lido não a contiver. O piso é honesto sobre o que
mediu; ele não adivinha o que não leu.
Não ler o relatório, sozinho, não é motivo de falha: o POST foi aceito, a retomada cobre o
resto e o run sai 0. O sinal de falha é a recusa, não a ignorância.
Se o próprio POST falha, o push é interrompido — e o que já assentou não morre com o erro. Os
dois modos de morte do transporte estão cobertos: o status de erro (5xx, limite) e a rede caindo
(ECONNRESET, DNS, timeout do runner), que é o caso mais comum. O run sai 1 e relata os lotes
concluídos, o piso apurado e as recusas que já conhecia — e quando nada foi apurado diz
não verificado, nunca ao menos zero, pela mesma razão de sempre: zero é uma afirmação.
(push interrompido) distingue esse caso do (relatório incompleto), que é o push que terminou
sem conseguir ler tudo. Re-rodar é seguro: a ingestão é idempotente e o GET :estado retoma do
último assentado.
Perdas
Toda perda é contada e sai por stderr — linha de merge não parseada, range sem commits, commit não parseado. Um corpus com buraco não pode ser indistinguível de um corpus completo.
