fix(archive): teto de 15 min no download do reparo e caminhos do runbook (C246-followup)

O canário 80/83 abortou no download do Flickr: original de ~54 MB (panorâmica 7952px) a ~260 KB/s no link direto do homeserver leva ~3,5 min, acima do teto de 2 min herdado do C231. ARCHIVE_INTEGRITY_DOWNLOAD_TIMEOUT_MS vira 15 min (exportada e pinada no unit); falha de download segue failed retentável. Runbook §C246 usa ~/archive-reports (o /srv do texto original não existe/grava) e ganha docker pull do migrator (a limpeza pós-deploy remove a tag local).
This commit is contained in:
Francisco Solla 2026-10-02 16:51:54 -03:00
parent a3a49c3462
commit 255751f5ce
4 changed files with 27 additions and 8 deletions

View file

@ -0,0 +1 @@
**C246-followup — teto do download do reparo e caminhos do runbook (2026-10-02):** o primeiro uso real do `pnpm archive:integrity --apply` em produção (canário 80/83) abortou os dois reparos no `download` do Flickr: o original é uma panorâmica de 7952px com **~54 MB** e a egress direta do homeserver estava a ~260 KB/s (~3,5 min), acima do teto de 2 min herdado da ingestão C231. O teto do download do reparo vira `ARCHIVE_INTEGRITY_DOWNLOAD_TIMEOUT_MS = 15 min` (constante exportada e pinada no unit), que ainda pega conexão travada sem cortar link lento-mas-funcional; falha de download continua `failed` retentável (nunca rebaixa). O runbook §C246 passa a usar `~/archive-reports` (o `/srv/archive-reports` do texto original não existe e não é gravável pela `fsolla`) e ganha o `docker pull` do migrator antes dos comandos — a limpeza pós-deploy remove a tag local e o registry preserva a imagem. Sem migration/UI; o restante da entrega (varredura + reparo + rebaixamento) fica intocado.

View file

@ -1438,7 +1438,7 @@ docker compose --profile maintenance run --rm \
```
Os recibos JSON ficam em `data/archive/reports/` do container — monte um volume
(`-v /srv/archive-reports:/app/data/archive`) para preservá-los. Guardas: fora de
(`-v ~/archive-reports:/app/data/archive`) para preservá-los. Guardas: fora de
teste o `TEQO_ENV=staging|production` é obrigatório com o **nome exato** do banco
(`teqo_staging`/`teqo_1313`); `ALLOW_REMOTE_DB` é recusado; sem as 4 `S3_*` o
`--apply` recusa (o acervo não estaria acessível no alvo); `ARCHIVE_CATALOG_CONFIRM=1`
@ -1702,24 +1702,26 @@ canário primeiro:
ssh homeserver
cd ~/stack
# 1) canário read-only nas fotos do achado (80/83) — não escreve:
# (a limpeza pós-deploy remove a tag local do migrator; o registry a preserva)
docker pull "$(docker inspect -f '{{.Config.Image}}' teqo-1313 | sed 's/teqo-1313:/teqo-1313-migrator:/')"
docker compose --profile maintenance run --rm \
-v /srv/archive-reports:/app/data/archive \
-v ~/archive-reports:/app/data/archive \
teqo-1313-migrate pnpm archive:integrity --only 80,83
# 2) reparo do canário (exige TEQO_ENV + confirmação; as 4 S3_* e FLICKR_API_KEY):
docker compose --profile maintenance run --rm \
-e TEQO_ENV=production -e ARCHIVE_INTEGRITY_CONFIRM=1 \
-v /srv/archive-reports:/app/data/archive \
-v ~/archive-reports:/app/data/archive \
teqo-1313-migrate pnpm archive:integrity --apply --only 80,83
# 3) conferir as duas URLs públicas respondendo 200 (antes do lote):
# https://jorgesolla1313.com.br/fotos/80/midia e /fotos/83/midia
# 4) varredura completa (read-only; ~6,5k downloads no bucket local; sai 1 se achar):
docker compose --profile maintenance run --rm \
-v /srv/archive-reports:/app/data/archive \
-v ~/archive-reports:/app/data/archive \
teqo-1313-migrate pnpm archive:integrity
# 5) reparo do que a varredura acusou (mesma linha com --apply):
docker compose --profile maintenance run --rm \
-e TEQO_ENV=production -e ARCHIVE_INTEGRITY_CONFIRM=1 \
-v /srv/archive-reports:/app/data/archive \
-v ~/archive-reports:/app/data/archive \
teqo-1313-migrate pnpm archive:integrity --apply
# 6) convergência: re-rodar a varredura; 0 pendências = aceite
```
@ -1731,6 +1733,9 @@ varredura mediria o disco efêmero do container); `ALLOW_REMOTE_DB` é recusado.
`--concurrency` (default 3) e `--limit`/`--only` são os canários; o recibo traz
varridas/ok/ausentes/corrompidas/removidas, e no reparo recuperadas/
irrecuperáveis/falhas. A varredura **não** conserta nada: só o `--apply` escreve.
O download do original no reparo tem teto de **15 min** — um original grande
(~50 MB, ex. panorâmica 7952px) leva minutos no link lento da casa; falha de
download é `failed` retentável, nunca rebaixamento.
Depois de um `--apply` que rebaixou alguma foto, buste a tag pública em um
processo que a possui (o write do CLI não revalida o servidor Next):

View file

@ -23,7 +23,13 @@ import { FLICKR_USER_AGENT, isFlickrSourceGone } from './flickrApi.mjs'
/** A photo original is MBs; the guard only catches a stalled/runaway server. */
const PHOTO_DOWNLOAD_MAX_BYTES = 512 * 1024 * 1024
const PHOTO_DOWNLOAD_TIMEOUT_MS = 2 * 60_000
/**
* Flickr originals can pass 50 MB (a 7952px panorama was 54 MB) and the
* homeserver's direct egress runs ~260 KB/s — ~3,5 min for that file — so a
* 2 min ceiling aborted a legitimate repair (C246 ops, 2026-10-02). 15 min
* still catches a stall without capping a slow-but-working link.
*/
export const ARCHIVE_INTEGRITY_DOWNLOAD_TIMEOUT_MS = 15 * 60_000
/** Long edge of the decode proof; the same size the face index feeds the engine. */
const DECODE_MAX_EDGE = 1024
@ -92,7 +98,7 @@ export const repairArchivePhotoFromSource = async ({
url: source.url,
destinationPath: filePath,
headers: { 'User-Agent': FLICKR_USER_AGENT },
timeoutMs: PHOTO_DOWNLOAD_TIMEOUT_MS,
timeoutMs: ARCHIVE_INTEGRITY_DOWNLOAD_TIMEOUT_MS,
maxBytes: PHOTO_DOWNLOAD_MAX_BYTES,
})
} catch (error) {

View file

@ -5,7 +5,10 @@ import { tmpdir } from 'node:os'
import { basename, join } from 'node:path'
import { afterAll, describe, expect, it, vi } from 'vitest'
import { repairArchivePhotoFromSource } from '../../scripts/lib/archiveIntegrityRepair.mjs'
import {
ARCHIVE_INTEGRITY_DOWNLOAD_TIMEOUT_MS,
repairArchivePhotoFromSource,
} from '../../scripts/lib/archiveIntegrityRepair.mjs'
import { FlickrApiError } from '../../scripts/lib/flickrApi.mjs'
import { ARCHIVE_PHOTO_JPEG_BYTES } from '../helpers/archivePhotoFixture'
@ -112,6 +115,10 @@ describe('repairArchivePhotoFromSource (C246)', () => {
expect(result.sha256).toMatch(/^[0-9a-f]{64}$/)
expect(callOrder).toEqual(['source', 'download', 'repair', 'inspect'])
expect(basename(vi.mocked(deps.repair).mock.calls[0][1].filePath)).toBe('flickr-123.jpg')
// A >50MB Flickr original does not fit the old 2 min ceiling on a slow link.
expect(vi.mocked(deps.download).mock.calls[0][0].timeoutMs).toBe(
ARCHIVE_INTEGRITY_DOWNLOAD_TIMEOUT_MS,
)
})
it('withdraws when Flickr refuses the source (photo gone)', async () => {