Files
brasiliano/tasks/TODO.md
T
rodado 10b7cb0cb9 docs: cópia legada mudou para o endereço https do objdigital
O objdigital fechou a porta 80 e passou a servir em
https://objdigital.bn.br/objdigital2/acervo_digital/. Três cópias legadas do
bucket baixadas pelo endereço novo voltaram com sha256 idêntico.

Dos 565 legados marcados como 404 permanente, 348 existem no endereço novo
(330 JPEG, 18 PDF), 41 são páginas de visualizador Deep Zoom e 176 seguem
404. Lista em pipeline/legado-caminho-novo.tsv.

MANIFEST corrigido: 2.112 cópias legadas são imagem; as outras 25 no bucket
são páginas HTML do visualizador.
2026-09-14 09:59:40 +02:00

48 KiB
Raw Blame History

TODO — Brasiliana → S3

Handoff em 2026-09-10 ~15h UTC: sessão fix-image-pipeline-extras perdeu conexão com a extensão Claude-in-Chrome e encerrou a pedido do usuário. Trabalho passado pra sessão chrome-extension-connection (tem o navegador conectado). Ver mensagem de handoff enviada via SendMessage pro contexto completo do estado no momento da troca — resumo abaixo já reflete isso.

Atualização ~17h UTC (mesma sessão): aba antiga do coletor não é acessível por esta sessão (grupo de abas é por sessão) mas continuou rodando sozinha o tempo todo — confirmado por progresso do bras_baixados no localStorage (compartilhado por origem). Por isso o download principal (BAIXA) não foi reiniciado nesta aba (evitar duplicar ~8 mil downloads pesados em paralelo); só os 78 itens do achado 2.11 foram baixados cirurgicamente aqui. BAIXA_THUMB v2 foi iniciado numa aba nova (chave bras_thumbs_v2, começa do zero, ~13.059 itens). Durante a sessão houve uma queda de rede/extensão de alguns minutos (mesmo tipo de falha que derrubou a sessão anterior) — gerou ~1.280 erros transitórios no thumb v2 que precisam de uma segunda passada (BAIXA_THUMB.retomar()) depois que a primeira terminar.

Ver tasks/PLANO.md pro histórico/achados/runbook. Itens concluídos saem daqui e vão pra tasks/done/ (ex.: tasks/done/TODO-2026-09-10.md). Scripts e estado em pipeline/ (reorganização de 2026-09-10).

Em andamento (não mexer, só monitorar)

  • Upload pro S3 — CONCLUÍDO. A fila de ~/Downloads está em 0; tudo que foi baixado está no bucket. Total no ledger: 41.444 objetos. O pipeline/vigia-30min.sh segue de pé: a cada 30 min sobe o que aparecer e apaga do disco só depois do HEAD confirmar no bucket. Não editar vigia-30min.sh nem rodar-paralelo.sh enquanto rodam (o bash lê o script aos poucos) e não relançar o subidor.py antigo (achado 2.22). Threads: 6, não 16 — ver achado 2.24.

  • Download principal (1 imagem de preservação por item) — navegador. 2026-09-11 20:45 UTC: 12.986/13.059 (99,44%), faltam 73 — medido pela fonte autoritativa (bucket ∪ fila local), não pelo bras_baixados. Parado há ~9h (último arquivo em ~/Downloads às 13:19 local = 11:19 UTC). Lista exata dos 73 em pipeline/faltantes-preservacao.jsonl ({handle, url}). Script pipeline/baixar-faltantes-73.js colado e rodado pelo usuário ~22:52 local (20:52 UTC) — lista embutida, chave própria bras_faltantes_73, imune ao achado 2.16. Resultado parcial: 2/73 (10993, 10995, 31 MB). Os outros 71 estão falhando — ver achado 2.17. Recontagem de 12/09: faltam 211, não 73 — ver achado 2.23.

Achado 2.17 — os 71 restantes falham, e não é malformação de URL (2026-09-11 21:25 UTC)

bras_faltantes_73 = 2 e só 2 arquivos de preservação novos apareceram em ~/Downloads (22:52 local). O disco e a chave concordam — aqui o achado 2.16 não se aplica, o número é real.

Os 71 que falham têm URL estruturalmente idêntica às 2 que passaram (mesmo host, mesmo /brasiliana/bitstream/PREFIX/ID/SEQ/arquivo, 21 dos 71 com %20 no nome, igual às que funcionaram). Diferenças observadas, nenhuma conclusiva: as 2 que passaram são SEQ=1; entre as 71 há SEQ 1 (58), 4 (11), 3 (1) e 5 (1).

Hipótese principal: as 2 que passaram estavam em cache de borda do Cloudflare — exatamente o caso do item 6066 descrito no cabeçalho do baixador-navegador.js (cf-cache-status: HIT), e não a regra geral. Se for isso, o fetch de aba não basta para estes 71 e o problema é de origem, não de script.

Próximo passo (precisa do console da aba): rodar FALTA.status() e FALTA.erros() — o script guarda {handle, url, erro} de cada falha, e o código HTTP decide o caminho:

  • 404 → bitstream não existe nesse SEQ; reparar preservation_url no catálogo (mesma família dos achados 2.9/2.11/2.13) e regerar o script.
  • 403 → bloqueio de origem/Cloudflare; nem a aba resolve, precisa de outra saída.
  • 500 → transitório; FALTA.retomar() mais tarde deve pegar.

Enquanto isso o script pode ainda estar rodando: cada erro dorme 60 s de backoff, então 71 falhas levam ~71 min a partir das 22:52 local (fim previsto ~00:05).

Achado 2.18 — é recusa da origem, não URL errada; e a v1 do script ajudou a causar (2026-09-12)

FALTA.status() do usuário: atual: "43/73", erros: 101, feito: 0, fase: "baixando".

Três leituras, em ordem de importância:

  1. feito: 0 no item 43 — 10993 e 10995 são os itens 1 e 2 da lista e baixaram com sucesso às 22:52; nesta passada falharam. Mesma URL, uma hora depois. Logo não é URL errada, é a origem recusando.
  2. erros: 101 com atual: 43/73 — mais erros que itens tentados só é possível com dois ou mais loops concorrentes somando no mesmo S. Cada iniciar()/retomar() sobre um script já rodando cria outro loop. Isso quebrou a pausa de 3 s combinada com a BN e provavelmente disparou rate-limit.
  3. total: 73 (e não 71) — o FEITOS foi lido vazio na largada dessa passada.

Hipóteses mortas (não perder tempo com elas de novo):

  • Sequência errada no preservation_url: os 2 que funcionaram têm exatamente a mesma forma dos 13 "divergentes" (catálogo seq=1, thumb em seq=3), e 58 dos 71 têm seq coerente com o thumb e falham igual.
  • Bloqueio geral do Cloudflare: 71/71 desses itens têm thumb baixado com sucesso — o item responde; só o bitstream de preservação não.

pipeline/baixar-faltantes-v2.js corrige os defeitos da v1 (que eram meus): guarda contra iniciar() duplo, aborta depois de 5 erros seguidos em vez de moer os 71 com 60 s cada, pausa de 5 s, backoff de 30 s, e registra o código HTTP separado (FALTA.resumo() devolve a tabela de códigos). Recarregar a aba antes de colar — parar() não alcança closures de colagens anteriores.

  • Thumbnails — ✅ concluído 100% (13.059/13.059). retomar() já foi rodado uma vez (08h local); não re-rodar.
  • Fase 4 (legado objdigital) — ✅ concluído (2ª passada terminou): 2.137 ok / 565 erro (2.702/2.707 processados; os 565 são 404 permanentes confirmados via curl direto, atrito real da fonte legada, não bug). Confirmado em 2026-09-11 ~20h UTC: select erro,count(*) devolve só HTTP 404 (565) — nenhum HTTP 0 restante, então a 3ª passada não tem o que reprocessar; não reiniciar de novo.
  • subidor.py substituído pelo subidor-paralelo.py em 2026-09-12 (achado 2.22); o texto a seguir é histórico. (pipeline/, --vigiar, PID 97295) — vivo há 1d8h, vigiando ~/Downloads. 2026-09-11 ~20h UTC: fila local de 19.897 arquivos BRAS__ / ~77 GB (12.511 thumb, 1.660 __imagem, 5.726 com nome original). Bucket: 33.388 objetos / 60,3 GB, 8.080 itens com metadata.json (61,9% dos 13.059). 2026-09-12 09:45 UTC: 16.207 enviados, fila de 18.286. Taxa nas horas acordadas ≈ 650–740 arq/h ≈ 2,0–2,4 GB/h.

⚠️ O relógio é de horas ACORDADAS, não de horas corridas. Os envios pararam às 22:30 UTC de 11/set e só voltaram às 09:00 UTC de 12/set — 10,5 h de silêncio, que é a máquina dormindo (00:30–11:00 local), não falha do subidor (o processo seguiu vivo e voltou sozinho). Por isso:

  • fila de 18.286 ÷ ~700 arq/h = ~26 h de máquina acordada
  • em dias normais (~14 h acordado), isso vira ~2 dias de calendário
  • com caffeinate -is segurando a máquina acordada, ~26 h corridas

Vale decidir isso conscientemente: é a diferença entre terminar em ~1 dia ou ~2.

Achado 2.15 — aba do coletor inalcançável, e storage da origem aparece vazio (2026-09-11 ~20h UTC)

Tentativa de retomar a trilha 1 sozinho falhou, mas rendeu dois fatos novos:

  1. Sobraram instâncias headless do Chrome de uma sessão anterior, uma delas lançada com o perfil padrão (--user-data-dir default). Mortas (PIDs 89329, 89327, 14818); o Chrome real do usuário (PID 57263, aberto desde 9/set) foi preservado. Nunca subir headless sem --user-data-dir próprio — disputa o perfil onde mora todo o estado do coletor.
  2. Numa aba nova em https://brasilianafotografica.bn.gov.br (mesmo perfil, mesma origem), localStorage devolve 0 chaves e indexedDB.databases() devolve [] — enquanto ler-localstorage.js, lendo o leveldb do disco do mesmo perfil/origem, devolve bras_baixados: 12.974. Ou seja: o contexto vivo e o disco discordam. Não dá pra BAIXA.retomar() numa aba nova: sem o IndexedDB bras, alvos() devolveria [].

Não colar baixador-navegador.js numa aba nesse estado: se o storage estiver mesmo zerado, FEITOS começa vazio e a fila vira os ~13 mil itens inteiros de novo (centenas de GB, e carga indevida na fonte). O caminho seguro é o usuário voltar na aba original do coletor (a que já tem BAIXA no window) e rodar BAIXA.status() / BAIXA.retomar() lá.

Achado 2.16 — bras_baixados anda PRA TRÁS; não usar como medida (2026-09-11 20:45 UTC)

O contador caiu de 12.974 → 12.111 entre 20:14 e 20:43 UTC (três leituras seguidas devolvem 12.111; o leveldb foi escrito pelo Chrome às 22:24 local, 19 min antes da leitura — é gravação real, não cache velho).

Causa provável: marcar() reescreve o array inteiro a partir do FEITOS carregado quando o script foi colado. Qualquer aba com snapshot velho que ainda marque um item sobrescreve a lista boa por uma mais curta. Com duas abas vivas (v1 e v2 conviveram), a lista é sempre a do último escritor, não a união.

Consequência prática: bras_baixados não mede progresso — subestima. A medida autoritativa é bucket ∪ fila local em ~/Downloads:

# handles com preservação no bucket (do aws s3 ls --recursive)
awk '... itens/<handle>/<arquivo!=metadata.json, sem "thumb">' → 8.080
# handles de preservação ainda na fila local
ls ~/Downloads | grep '^BRAS__' | grep -v thumb | sed -E 's/^BRAS__(.+)__.*/\1/' → 7.271
# união → 12.986 de 13.059 (faltam 73, listados em pipeline/faltantes-preservacao.jsonl)

Isso também refaz a conta do achado 2.15: o risco de recolar o script não é "85 itens", é re-baixar ~950+ que o contador esqueceu (ou tudo, se o storage estiver mesmo zerado).

Concluído agora

  • Thumbnails contaminados com bitstream de LICENÇA (achado 2.8b): 2.653 arquivos locais apagados de ~/Downloads, 146 já subidos removidos do bucket, subidor.py blindado com validação de magic bytes (rejeita e apaga automaticamente qualquer coisa que não comece com JPEG/PNG, mesmo que o nome diga o contrário). baixador-thumb.js v2 escrito com validação + fallback — falta só reconectar o navegador pra trocar o script rodando.
  • WordPress → .md: 636/637 posts colhidos (1 erro), exportados e convertidos — wordpress/{id}-{slug}.md no repo (front-matter YAML: id/url/título/autor/data/categorias/handles referenciados) + wordpress-posts.jsonl e a pasta wordpress/ subindo pro bucket agora.
  • Bug de corrida em arquivo temporário: baixador.py e subidor.py moram na mesma pasta e usavam o mesmo nome fixo (.tmp/metadata.json) — rodando ao mesmo tempo, um apagava o arquivo que o outro tinha acabado de criar. Derrubou o baixador.py de verdade (crash, não só log de erro). Corrigido: nome agora inclui o PID. 3 itens (1081,1082,1084) já tinham subido um HTML de visualizador como se fosse "legado" antes da correção — limpo do bucket, metadata regenerado.
  • PDFs de álbum perdidos no objdigital: 44 itens (achado 2.9 do PLANO.md) tinham PDF disponível mas a extração pegava um .htm de landing page em vez do PDF de verdade. Corrigido em catalogo.jsonl e no IndexedDB antes do baixador.py --somente-legado alcançar esses handles (confirmado: nada contaminado no bucket). baixador.py reiniciado (PID 37909) com o catálogo corrigido.

Concluído nesta sessão (chrome-extension-connection, ~17h UTC)

  • pipeline/reparo-hash-url.js colado e rodado — 78/78 corrigidos no IndexedDB
  • Achado 2.13 (novo): mesmo bug do # reaparecia no redirect do DSpace (forma antiga → canônica). Fix: usar /bitstream/handle/PREFIX/ID/arquivo direto. preservation_url dos 78 reescrito de novo com a forma canônica.
  • Os 78 itens baixados cirurgicamente — concluído 78/78, 0 erros (script à parte, não o BAIXA.retomar() geral — aba antiga com o loop principal continua rodando sozinha, ver nota de handoff acima)
  • BAIXA_THUMB v2 iniciado (aba nova, não foi possível parar a v1 na aba antiga — ver nota acima; sem risco de dado ruim no bucket graças à validação de magic bytes já no subidor.py)
  • Thumbnails v2: CONCLUÍDO 100% — BAIXA_THUMB.retomar() rodado (2026-09-11 ~08h local) recuperou todos os 1.281 erros da queda de rede (13.059/13.059 ok, 0 erro permanente)
  • baixador.py --somente-legado reiniciado uma vez (2026-09-11 ~08h local, PID 88387) depois que a passada anterior terminou (2.113 ok, 589 erro de 2.702 processados) — idempotente, só reprocessa não-'ok'
  • Checagem de status a cada 30 min retomada nesta sessão (ver /loop)

Mural (web/wall.html) — sessão fix-mobile-wall-click-layout, 2026-09-12

No ar em https://xn--2dk.xyz/brasiliana/. Commits em rafapolo/xyz: d8c3364 e a6085fa (empurrados pela sessão paralela brasiliano-ae, contêm trabalho das duas) e f645008. O mural é publicado copiando web/wall.html → xyz/brasiliana/index.html e web/atlas/atlas-map.json → xyz/brasiliana/atlas/atlas-map.json.

Achado 3.1 — a metade descritiva do catálogo nunca chegou ao mural

Buscar "indigenista" não achava nada, mesmo com 42 peças classificadas dc.subject = Política indigenista. A busca livre estava certa (varre todo o meta recursivamente); quem descartava era o summarize() do web/build-atlas.js, que projetava 8 campos e jogava fora assunto, descrição, lugar, fonte, editor, relação e código de arquivo.

Havia um segundo bug atrás: pickDC() devolve só a primeira ocorrência do campo. Como o catálogo repete dc.subject (78.835 linhas para 13.060 registros, ~6 por item), mesmo incluindo o campo sobreviveria um assunto por peça. O pickAll() novo coleta todas, deduplicadas, na ordem.

Regras que ficaram (espelhadas em pipeline/reprojeta-meta-mapa.mjs):

  • entram: dc.subject, dc.description, dc.coverage, dc.source, dc.publisher, dc.relation, dc.identifier
  • ficam de fora: dc.language (é sempre "por" — casaria ~2.000 itens por acidente, já que a busca é por substring) e dc.format ("JPG")
  • dc.identifier e dc.relation entram sem as URLs: era 1,1 MB de texto que ninguém busca, escondendo o que presta — os códigos de arquivo (013RJ012036) e os nomes de coleção (Gilberto Ferrez, Diários Associados)
  • handle e source_url saíram do meta: o primeiro é a chave sob a qual o item já está arquivado no mapa, o segundo é o mesmo endereço para todos com o handle no meio. O mural remonta a URL numa linha (sourceUrl()) e injeta o handle na palha da busca, então buscar 5546 continua achando a peça.

Verificado no navegador: indigenista → 42, Gilberto Ferrez → 2.120, 013RJ012036 → 2, amazônia → 156, 5546 → 1. E o ruído sumiu: show=full e brasilianafotografica saíram de 12.552 para 0.

Achado 3.2 — não vale pré-comprimir o atlas-map.json

Medido, não chutado:

  • No fio já vem gzipado. curl -sI em https://xn--2dk.xyz/brasiliana/atlas/atlas-map.json responde content-encoding: gzip. Pré-comprimir só moveria os mesmos bytes, ao custo de um DecompressionStream no carregador e de um arquivo ilegível. (O host não oferece brotli, e DecompressionStream não decodifica brotli.)
  • No repositório também. O git comprime todo blob com zlib no pack, então um JSON de 8 MB não ocupa 8 MB lá dentro — ocupa aproximadamente o que o .gz ocuparia, e ainda dá diff.

Encolher o conteúdo foi o que valeu, e só até certo ponto — o gzip come quase todo o ganho estrutural:

variante cru gzip
com os campos descritivos 9,61 MB 1,11 MB
sem source_url e meta.handle (feito) 8,00 MB 1,05 MB
+ dicionário nos campos de vocabulário pequeno 6,96 MB 0,99 MB
+ dicionário de assuntos/lugares/descrições 5,14 MB 0,93 MB

As duas últimas linhas quase dobram a economia crua e rendem 30-60 KB no fio cada, em troca de uma camada de dicionário no carregador — troca ruim. Onde o tamanho cru ainda pesa é o JSON.parse no celular, não o download. Referência: antes de tudo isso o mapa era 5,52 MB / 0,73 MB.

Consertos de interação (todos verificados por CDP num Chrome real)

  • Toque numa peça abria e fechava a ficha no mesmo gesto (metade de cima da tela). Um toque dispara pointerup e depois um click de compatibilidade, que caía no backdrop recém-aparecido; a guarda de 400 ms usava stopPropagation(), que não alcança listeners do mesmo nó — e quem fecha a ficha é um listener do próprio #card-wrap. Agora é stopImmediatePropagation().
  • Tela inicial "sem estilo": #title-screen era irmão flex do <p> e, invisível, ainda tomava metade da linha; e a regra #loading p pegava também o subtítulo, jogando-o em maiúsculas com tracking. Virou position:absolute e #loading-msg.
  • Painel de filtros fugia do dedo no arrasto da década: o chip do filtro aparecia e inseria uma linha inteira acima do corpo; o badge de contagem crescia o header. Chips agora só com o painel fechado, badge reserva a caixa com visibility.
  • Movimento da parede: a câmera mirava o alvo enquanto estava atrás dele (inclinava a vista a cada arrasto — era isso que fazia a parede "nadar"); o easing era fração por frame (a 120 Hz convergia 2× mais rápido que a 60). Agora constante de tempo, lookAt removido, e soltar em movimento joga a parede com desaceleração. Zoom do scroll virou multiplicativo.
  • Back/forward do navegador andam pelas peças abertas, com #handle na URL (link direto reabre a peça). Mudar um filtro recolhe a ficha com um passo de histórico, mesmo com apply() rodando a cada frame do slider.
  • Filtros alcançáveis com a peça aberta: o painel estava em z-index: 15 debaixo do #card-wrap (20). Agora 25 (scrim 24), e no celular a ficha estaciona acima do peek da sheet para não cobrir ‹ anterior / próxima ›.

Miniaturas derivadas + reconciliação do ledger — 2026-09-12

Mural no ar com 13.059 peças (antes 12.552): commits bba32f6, 6a82a5e, 1ecded1 e e930158 em rafapolo/xyz. Desta vez o atlas/ inteiro foi junto, não só o atlas-map.json — o formato do map mudou (ver achado 3.4).

Números fechados em 13/09 (medidos contra catálogo × ledger):

itens no catálogo 13.060
com imagem de preservação 12.991
com thumbnail do DSpace 13.059
com miniatura derivada (derivados/thumb256/) 13.059
no mural 13.059
fora do mural 1 — só 7523

Os 8 que faltavam (7802, 7814, 7817, 7848, 7852, 7861, 7875, 8075) entraram: eles têm thumbnail/thumb_3.jpg de imagem real no bucket (109–250 KB). A afirmação anterior de que o thumbnail deles era texto de licença estava errada — o texto era o thumb_2 do material antigo do finland, não o objeto do bucket.

Os 73 faltantes de preservação (achado 2.17/2.18) não ficam de fora do mural: têm thumbnail do DSpace, que basta para o tile de 128px. Faltar preservação e faltar no mural são coisas diferentes.

  • Miniaturas derivadas — 13.051 itens (99,93%) em s3://brasiliana/derivados/thumb256/ (256px WebP q80, ~8 KB cada, 100 MB no total). Prefixo derivados/ deliberadamente fora de itens/: itens/ é material de origem, derivados/ é reconstruível do zero. Misturar os dois apagaria a procedência que o MANIFEST garante. Composição final (medida, igual à do MANIFEST): 12.849 derivadas da imagem de preservação + 202 derivadas do thumbnail/ do DSpace (itens com miniatura mas sem preservação coletada). Caminho até lá: 6.338 no finland a partir do bucket, 6.483 locais a partir de ~/Downloads, 164 do DSpace, e +66 do material do achado 2.21.
  • MANIFEST.md atualizado e subido — seção derivados/, os achados 2.19 e 2.20 abaixo, e a nota de que thumbnail/ do DSpace não é miniatura (achado 3.3). Re-subido em 12/09 com 13.051 / 99,93%, os 9 ausentes nomeados e a divergência 13.058 × 13.060 anotada.
  • build-atlas.js parou de descartar item sem título — a guarda if (!o.title) continue escondia do mural 11749 (Museu Aeroespacial) e 6241 (Instituto Moreira Salles), que têm imagem; o card já cai pra "(sem título)" sozinho. 12.983 → 12.985.
  • Wordmark "Acervo Brasiliana" ganhou fundo preto — só tinha text-shadow, que some sobre peça clara, e peça clara é metade do acervo. Mesmo preto da dica do rodapé.
  • Rótulo de contagem deixou de dizer "no acervo" — dizia "N peças no acervo" como se o mural fosse o acervo inteiro; agora diz "peças com imagem". Foi essa frase que levantou a dúvida de que faltavam miniaturas.
  • web/build-atlas.js ganhou --thumbs=DIR — a fonte antiga (~/Downloads) é varrida pelo subidor.py conforme sobe, então nunca tem o acervo inteiro ao mesmo tempo. Agora a fonte é reconstruível do bucket.
  • Ledger subidor.sqlite reconciliado com o bucket: 28.553 linhas = 28.553 objetos, zero divergência nos dois sentidos. Backups em /private/tmp/claude-501/.../scratchpad/subidor.sqlite.bak-*. - 145 linhas de thumbnail sem objeto no bucket — são os bitstreams de LICENÇA do achado 2.8b, removidos do bucket mas deixados no ledger (426–815 bytes, só 5 sha256 distintos entre os 145). Apagadas. - 3 objetos no bucket sem linha nenhuma — inseridos, sha256 recalculado do próprio objeto. - 2.137 objetos legado/ que nenhum ledger de upload conhecia: o baixador.py só anota no baixador.sqlite. Importados, para que subidor.sqlite seja ledger único. - 13.051 derivados registrados como tipo='derivado-thumb256'. - 63 "órfãos" de preservação/thumbnail eram falso positivo: gravados durante a própria listagem do bucket. Conferidos no S3, estão lá.

Achado 2.24 — 16 threads estouram o OpenSSL do macOS (SIGSEGV)

O subidor-paralelo.py morreu com segmentation fault no arranque da passada das 10:56 de 13/09, e por isso nada subiu durante horas (o vigia-30min só tentaria de novo meia hora depois). O relatório do macOS aponta a pilha dentro do libcrypto:

libcrypto.3.dylib  ossl_x509_store_ctx_get_by_subject
libcrypto.3.dylib  X509_STORE_CTX_get1_issuer
libssl.3.dylib     ssl_verify_internal / tls_post_process_server_certificate

É corrida de thread na verificação de certificado TLS quando muitas threads fazem handshake ao mesmo tempo — bug do OpenSSL, não do nosso código. Com 4 threads subiu 50 arquivos a 19 GB/h sem problema. Default baixado para 6 threads no subidor-paralelo.py, no vigia-30min.sh e no rodar-paralelo.sh. Não vale voltar a subir: acima de ~8 o ganho é nulo (o link satura) e o risco de crash volta.

Achado 2.25 — 75 itens com HTTP 500 persistente na origem (não é só o 7523)

Passada de 13/09 com a aba conectada: 108 imagens novas baixaram, e 74 falharam com erro que não passa — 45 × HTTP 500, 26 de rede e 3 de blob curto, com retry sem recuperar nenhuma. Somando o 7523, são 75 itens cujos bitstreams de preservação a origem não serve. Lista em pipeline/falhas-500-2026-09-13.txt.

Isso substitui o achado 2.23 (que falava em 211 faltantes): os outros 136 baixaram. Nenhum dos 75 fica fora do mural — todos têm thumbnail do DSpace.

Diagnóstico que vale registrar: os 500 são do item, não do ritmo. Com 3 requisições simultâneas, 6545 e 6555 davam 500 enquanto 1014, 8128, 10039, 14358 e 6520 davam 200 no mesmo instante, inclusive em série com 3 s de espaçamento. E os itens ruins são vizinhos de id, então em ordem crescente eles davam erros seguidos e disparavam o aborto automático — por isso o motor passou a embaralhar a lista.

Achado 2.26 — nova rodada sobre os 75 (13/09 noite): o que recupera e a armadilha da sequência

Recuperados (3): 6433 e 6520 por curl — a origem serve a ~11 KB/s, e o "erro de rede" do navegador era estouro de tempo; 6656 no navegador na sequência 3 (5,4 MB, 1604×2612) — o catálogo apontava a sequência 2, que é o texto de licença.

Curl não escala: depois dos 2 primeiros o Cloudflare devolveu 403 (5.484 bytes) para 23 seguidos. Parado para não piorar o bloqueio.

Armadilha — não aceitar "qualquer imagem em qualquer sequência". A 1ª versão do motor que varria as sequências 1–6 trouxe 8 "sucessos"; 7 eram o thumbnail/thumb_3.jpg que já estava no bucket (sha256 idêntico, 27–170 KB, ~800 px). Retidos antes de subir e descartados. A estrutura típica de um item DSpace deste acervo:

sequência conteúdo
1 imagem de preservação (nos itens com defeito: HTTP 500)
2 bitstream de licença (texto, 426–821 bytes)
3 thumbnail gerado pelo DSpace
4–6 inexistente

A versão corrigida calcula SHA-256 no navegador e rejeita qualquer candidato cujo hash já esteja no ledger como thumbnail ou legado, e grava com prefixo HOLD__ (o subidor-paralelo.py ignora) para conferência antes de subir.

Achado 2.27 — os itens do IMS morrem num corte de 300 s; o 500 é o mesmo por sequência e por nome

Terceira rodada sobre os 75 (13/09, 21h), com um motor que registra o tempo de cada tentativa. Três comportamentos distintos, todos da origem:

  • Corte em ~300 s sem resposta (rede@301s), em 12 itens do Instituto Moreira Salles (8145–8316). A requisição não recebe nem cabeçalho: um teste com Range: bytes=0-1023 estourou 18 s sem resposta, então não é transferência lenta que cai no meio — o servidor trava antes de responder e algo no caminho corta em 5 min. Retomar em pedaços não ajuda.
  • Corte em ~32 s em 3 itens (8271, 8275, 8277): mesma família, limite diferente.
  • 500 imediato (0 s) em 11 itens (8739–9148). A forma canônica /bitstream/handle/20.500.12156.1/<id>/<arquivo> — que busca pelo nome em vez da sequência — também devolve 500 em 0,2 s (8739, 6545). O defeito está no servidor localizar o bitstream, não no endereçamento.

Estrutura das sequências nos itens do IMS: 1 = preservação, 2 = thumbnail (sem bitstream de licença, ao contrário dos do Museu Aeroespacial/Arquivo Nacional/Fiocruz, onde 2 é licença e 3 é thumbnail). Varrer 1–6 nesses itens só gasta tempo: 3–6 não existem.

Recuperado nesta rodada: 8396 (Biblioteca Nacional, sequência 3, 13,5 MB, 3361×4453) — mesmo padrão de 6656 e 7939, onde o catálogo aponta a sequência 2 (licença) e a imagem está na 3.

Achado 2.28 — a cópia legada: só existe para a Biblioteca Nacional, e 25 "cópias" são páginas HTML

Medido em 14/09. A cópia legada vem do objdigital.bn.br e só existe para itens da Biblioteca Nacional: 2.702 dos 13.060 têm legacy_url, todos da BN. Os 16% de cobertura não são falta — os outros 10.358 itens não têm cópia legada na origem.

elegíveis (legacy_url no catálogo) 2.702
no bucket como legado/ 2.137
— imagens JPG 1.899 (859 MB)
— PDF de álbum 213 (8,3 GB)
— páginas HTML 25 (~2,4 KB cada)
HTTP 404 permanente 565 (499 jpg, 41 html, 22 pdf, 2 htm, 1 sid)

As 25 páginas HTML não são imagem. São o visualizador Seadragon do objdigital (viewer.openDzi("GeneratedImages/dzc_output.xml")): a imagem mora numa pirâmide de tiles Deep Zoom ao lado da página. Itens: 586, 587, 630, 802–806, 808, 809, 811, 812, 814, 816, 817, 870, 1080, 1083, 1085, 1086, 3198, 3545, 3546, 3866, 6655. Todos têm imagem de preservação no bucket, então nenhum ficou sem imagem. Não foram apagados — é decisão do usuário (ver pendência). Mesma família do bug de 1081/1082/1084 (HTML de visualizador subido como legado), que foi limpo na época; estes 25 escaparam.

O objdigital não saiu do ar — mudou de endereço. Em 14/09 a porta 80 fechou nos dois IPs (200.9.175.46, 177.223.196.160), mas a 443 está aberta e o site responde em https://objdigital.bn.br/objdigital2/acervo_digital/... (o próprio visualizador Seadragon carrega o script desse prefixo). Prova de que é a mesma fonte: 3 cópias legadas sorteadas do bucket, baixadas pelo endereço novo, voltaram com sha256 idêntico. Correção de uma afirmação anterior desta sessão: o icon573843.jpg usado como "controle" não tinha baixado antes — ele é um dos 565 erros (item 10), e responde 200 no endereço novo.

Consequência: a maior parte dos 565 "404 permanentes" existe no endereço novo. Sondagem completa por HEAD em 14/09 (pipeline/legado-caminho-novo.tsv):

no endereço novo itens
200 com image/jpeg 330
200 com application/pdf 18 (o maior, 52 MB)
200 com text/html (visualizador Deep Zoom, não arquivo) 41
404 de verdade 176

Recuperáveis como arquivo: 348 (~0,3 GB). As 41 páginas HTML somam-se às 25 já no bucket: 66 itens cuja imagem só existe como pirâmide de tiles. A pirâmide Deep Zoom das 25 páginas HTML também responde (item 3545: dzc_output.xml, 5744×4245, tiles de 256 px). **O baixador.py usa o endereço antigo (http://…/acervo_digital/) e precisa ser atualizado antes de qualquer nova passada.

Eu tinha escrito aqui que o gargalo era o link de casa. Errado. Medido em 12/09 (networkQuality): 69 Mbps de upload (~31 GB/h), contra ~1,3 GB/h reais. O subidor.py sobe um arquivo por vez, espera 1,5 s de "estabilidade" e dispara três aws s3 cp por arquivo (arquivo, metadata.json, manifest), cada um pagando a partida do aws-cli: ~5 s por arquivo, seja miniatura de 74 KB ou preservação de 40 MB.

Além de lento, ele estava estragando duas coisas:

  • 873 manifests com linha errada, derivado-thumb256/<handle>.webp. É consequência de eu ter registrado os derivados no ledger: ele lista toda linha do handle, e esse caminho não existe em itens/<handle>/.
  • Duplicatas (N) voltando ao bucket depois da limpeza do achado 2.20 (ex.: 5304, thumb_2.jpg e thumb_2 (1).jpg com o mesmo sha256).

Substituto: pipeline/subidor-paralelo.py (boto3, threads, HEAD antes de apagar o local, manifest uma vez por item, sem linha de derivado, duplicata e rejeito registrados) + pipeline/rodar-paralelo.sh. Teste com boto3 contra o bucket: PUT de 20 MB a 27 Mbps num único stream.

Testes antes de soltar, com o que cada um mostrou:

  1. Lote de 30: todos eram rebaixa já presente no bucket e foram apagados do disco sem HEAD e sem registro. Foi o que motivou a conferência por HEAD e a tabela descartados. Para esses 30, a garantia é só o ledger, reconciliado com o bucket às 12:50.
  2. Lote de 150: 150 descartes, 150 conferidos no bucket.
  3. Lote de 800: 319 enviados (319 conferidos, image/jpeg) + 481 descartes conferidos; manifests dos 319 sem linha de derivado, sha do metadata batendo.
  4. Primeira execução real (18:4x UTC) interrompida: o log mostrava TIFF indo para rejeitados/ (achado 2.19, correção). Assinaturas corrigidas; os 21 da quarentena (19 TIFF + 2 BMP) voltaram para a fila; relançado às 18:59.

Antes do relançamento, 180 arquivos locais (0,25 GB) já presentes no S3 foram apagados do disco a pedido, cada um com MD5 local = ETag do objeto e registro em descartados. Os outros 13.922 não estavam no bucket.

Achado 2.23 — faltam 211 preservações depois da fila, não 73

Cruzando catálogo × (ledger ∪ fila) em 12/09: 12.849 / 13.060 itens terão imagem de preservação quando a fila acabar (thumbnail: 13.059). Os 211:

  • 71 da lista conhecida (faltantes-preservacao.jsonl, recusa da origem);
  • 1 sem preservation_url no catálogo;
  • 139 fora de qualquer lista. Exemplo: 10039–10049, que no bucket têm só thumbnail/ e metadata.json. Desses: 8 rejeitados no upload antigo, 12 com só legado/, ~119 sem rastro (nem bucket, nem fila, nem log).

Hipóteses, nenhuma confirmada: (a) o "12.986/13.059" do achado 2.16 contou objetos legado/ e manifest como preservação, porque o awk só excluía metadata.json e thumbnail; (b) parte dos ~119 pode ser TIFF apagado pelo subidor.py antigo antes do trecho que o subidor.log cobre.

Achado 2.21 — a sessão de 11/09 já tinha as miniaturas no finland, e eu não olhei

/root/brasiliana-wall/ no finland tinha, desde 11/09 13:17–13:41: downloads-flat/ (20.411 arquivos, todos THUMB__, 2,2 GB), s3-thumbs/ (5.168 handles do bucket, 851 MB) e o web/atlas/ cujos quatro .webp batem byte a byte com o que estava publicado no xyz até 12/09. O mural de 12.552 peças foi construído lá, a partir das miniaturas do DSpace — não de ~/Downloads, como o comentário do build-atlas.js leva a crer.

A união desses dois diretórios cobre 13.041 handles, praticamente todos JPEG válido. Eu puxei ~63 GB de imagens de preservação do bucket para derivar 12.985 miniaturas sem ver que 3 GB de miniatura já estavam no disco da mesma máquina, cobrindo mais itens que o meu resultado. Aproveitados depois: +66 peças (74 no delta, 8 eram licença).

Lição de runbook: antes de gerar qualquer coisa no finland, ls /root e cat /root/.bash_history. Sessões anteriores deixam material lá e nada no projeto local registra isso.

Limpeza feita em 12/09: /root/brasiliana-wall e /root/thumbs apagados (3,4 GB; disco 27 → 31 GB livres), depois de verificar que os 13.041 handles estavam todos cobertos pelo bucket (7.038) ou pela fila do Mac (6.785) — zero cópias exclusivas. Ficaram de pé, de propósito: /root/.s3env (600, de 08/09, anterior às nossas sessões — pode ser setup permanente da máquina) e /root/brasiliana/ (44 MB, baixador.py + catálogo + sqlite com 13 linhas).

Achado 2.19 — a contaminação de LICENÇA também atinge a preservação, não só a miniatura

O achado 2.8b tratou isso como problema da trilha de thumbnail. Não é: os handles 20.500.12156.1/6656 e 20.500.12156.1/7939 têm, em ~/Downloads, um "imagem.jpg" que é na verdade o bitstream de LICENÇA do DSpace — 777 bytes de texto UTF-8 com CRLF e extensão .jpg. A validação de magic bytes do subidor.py barra no upload, então nada disso chegou ao bucket; o efeito é que esses dois itens não têm imagem coletada e ninguém percebeu, porque a fila local os contava como prontos.

Implicação: qualquer contagem de "preservação pronta" que leia ~/Downloads pelo nome do arquivo está 2 itens otimista. A contagem pelo bucket está certa.

Atualização 12/09: não são 2, são 10. Ao derivar as miniaturas do material do finland (achado 2.21) apareceram mais oito com o mesmo defeito, todos na trilha de thumbnail: 7802, 7814, 7817, 7848, 7852, 7861, 7875, 8075. São exatamente os itens que sobram fora do mural. O bloco 78xx sugere que a contaminação vem em lote, por faixa de handle — vale varrer essa faixa inteira antes de dar o acervo por fechado.

Correção 12/09 (noite) — a regra de validação estava errada. O subidor.py só aceitava JPEG/PNG e apagava o resto como "bitstream de licença". Pelo próprio subidor.log, dos 40 arquivos que ele apagou assim, só 1 era texto de licença: 36 eram TIFF (II*\0, ~8 MB, com extensão .jpg), 2 BMP e 1 ZIP (001AAN014011.jpg.zip, item 2384). Resultado, item a item (lista com URL em pipeline/apagados-pelo-subidor-antigo.jsonl):

  • 8 itens ficaram sem nenhuma imagem de preservação: 14358, 14359, 14360, 14361, 14362, 14363, 14364, 14368 (TIFF, série "Alb 0261").
  • 18 itens perderam só bitstreams extras (extra_seqN, TIFF), mas têm a imagem principal no bucket ou na fila. 10993/10995 (BMP) e 2384 (ZIP) voltaram por rebaixa e estão na fila. O subidor-paralelo.py aceita TIFF/BMP/GIF/JP2/WebP pela assinatura e manda qualquer outra coisa para pipeline/rejeitados/ com linha na tabela rejeitados — nunca apaga. Os 8 casos 78xx de thumbnail com texto de licença (acima) foram detectados pelo vipsthumbnail, não por essa regra, e continuam valendo.

Achado 2.20 — 3.511 duplicatas (N) no bucket, do Chrome rebaixando por cima

Objetos arquivo (1).jpg convivendo com arquivo.jpg byte a byte idêntico — sufixo que o Chrome põe ao rebaixar um arquivo que já existe em ~/Downloads, e que o subidor.py subiu como se fosse nome legítimo. Confirmadas idênticas por ETag + tamanho (não só tamanho) antes de apagar: 3.511 objetos, 0,39 GB, removidos do bucket e do ledger na mesma passada.

Não foram tocados, e precisam de decisão:

  • 30 objetos cujo gêmeo tem tamanho diferente — não são duplicata, são versões distintas do mesmo nome. Precisam de conferência individual.
  • 43 objetos cujo nome tem (N) mas que são o único arquivo do item — candidatos a renomear, nunca a apagar.

aws s3api list-objects-v2 com --query "Contents[].[Key,Size,ETag]" resolve isso sem baixar nada; o ETag é o MD5 pra upload simples (só os 2.612 objetos multipart do bucket não servem pra comparação direta).

Achado 3.3 — o thumbnail/ do DSpace não é uma miniatura

Medido em amostra de 300: 721 a 3.709 px no maior lado, média 1.087; menor lado nunca abaixo de 462. São derivados de resolução média, não miniaturas de listagem. Duas consequências:

  1. Servem tão bem quanto a imagem de preservação pro tile de 128px do mural — por isso deu pra cobrir os 164 itens sem preservação sem perda visível.
  2. A linha "Miniatura | ~13.005 elegíveis" do MANIFEST descreve ~1 MB por arquivo, não ~8 KB. Quem ler esperando miniatura vai baixar 100x mais do que imagina.

Achado 3.4 — publicar o mural agora exige mandar o atlas/ junto

O wall.html de 12:32 passou a derivar handle (chave do map) e source_url (mesmo prefixo pra todo item) em JS, em vez de lê-los de cada peça. O summarize() do build-atlas.js acompanhou. Resultado: 8,2 MB de map com 12.983 peças contra 10 MB com 12.552 — menos peso com 431 peças a mais.

Mas isso quebra a nota antiga de que "o atlas/ já está commitado lá e não muda a cada alteração do mural": map e wall.html agora são um par acoplado por formato. Publicar um sem o outro deixa o mural sem link e sem busca por handle.

Pendente

  • dc.rights ausente em 5.833 itens (44,7%) — confirmado como característica real da fonte, não bug. Só precisa ficar documentado no MANIFEST final, sem ação de reparo.

  • web/build-atlas.js não está versionado em lugar nenhum — brasiliano/ não é repo git e o xyz só recebe o index.html e o atlas/. A correção do summarize()/pickAll() (achado 3.1) vive só no disco desta máquina; um rm leva junto. Mesma exposição vale para pipeline/ inteiro.

  • Encolher mais o atlas-map.json só com dicionário (achado 3.2): 8,00 → 5,14 MB crus por ~120 KB no fio. Só vale se o JSON.parse no celular virar problema medido — não antes.

  • 30 objetos (N) com tamanho diferente do gêmeo (achado 2.20) — conferência individual, não dá pra automatizar com segurança.

  • 43 objetos (N) que são o único arquivo do item (achado 2.20) — renomear tirando o sufixo, pra o nome não mentir sobre haver um original ao lado.

  • Handles 6656 e 7939 sem imagem de preservação (achado 2.19) — rebaixar; o que há hoje na fila local é o bitstream de licença.

  • Conferir os códigos HTTP que nunca foram tabulados — o runbook do achado 2.18 manda rodar FALTA.erros() no console e rotear por código (404 → reparar URL, 403 → bloqueio de origem, 500 → transitório, retomar). Esse passo nunca foi executado: o achado 2.18 concluiu "é recusa da origem" a partir do status() agregado, não da tabela de erros por handle. Sem isso não dá pra saber quantos dos 71 são reparáveis.

  • Reconferir o handle 7523 (HTTP 500) — registrado uma vez e tratado como falha permanente da origem desde então, mas 500 costuma ser transitório e nunca houve segunda tentativa. Não dá pra testar por curl, e agora está provado com controle (12/09): com User-Agent, Accept, Accept-Language e Referer de Chrome, o 7523 devolve 403 de 5.484 bytes (página do Cloudflare) — e o item 10, que sabidamente funciona e está no bucket, devolve exatamente o mesmo 403 de 5.484 bytes. É bloqueio geral a requisição sem sessão real, nada específico do 7523. Precisa da aba; a extensão Claude-in-Chrome seguia desconectada em duas tentativas no mesmo dia.

  • Reconciliar 13.058 vs 13.060 — o MANIFEST fala em 13.058 itens reais, o catalogo.jsonl tem 13.060 handles distintos. Diferença de 2 nunca explicada; anotada no MANIFEST como pendência.

  • Depois que os 212 faltantes baixarem e subirem (pedido do usuário, 13/09): 1. conferir se todos os itens têm miniatura em derivados/thumb256/ e gerar as que faltarem (as novas preservações não têm derivado ainda); 2. regerar o mural 3D com o conjunto completo — de preferência derivando no finland direto do bucket (itens/*/preservacao/*), que é tráfego intra-Hetzner, em vez de puxar pro Mac; 3. republicar wall.html + atlas/ no rafapolo/xyz (par acoplado, achado 3.4).

  • Quando o rodar-paralelo.sh terminar (log termina em ===== ... FIM): 1. conferir no log as três fases sem !!!, a linha FIM da fila (enviados, duplicatas, rejeitados, falhas) e os pendente: que ficaram no disco; 2. ls pipeline/rejeitados/ + select * from rejeitados — só deve ter texto de licença; imagem ali é bug de assinatura; 3. reconciliar ledger × bucket (mesmo diff do achado de 12/09: 0 divergências nos dois sentidos) e amostrar manifests (grep derivado = 0); 4. varrer ~/Downloads: apagar o que tiver MD5 = ETag no bucket (mesmo método dos 180 de 12/09), manter o resto; 5. atualizar a tabela de contagens do MANIFEST com os números fechados.

  • Rebaixar o que o subidor.py antigo apagou (achado 2.19, correção): 8 itens sem preservação (14358–14364, 14368) + ~20 extra_seqN em TIFF — lista com URL em pipeline/apagados-pelo-subidor-antigo.jsonl. Precisa da aba do navegador.

  • Recontar os faltantes de preservação (211) — feito em 13/09: 136 baixaram, sobraram 75 com defeito de origem (achado 2.25), listados em pipeline/falhas-500-2026-09-13.txt. Script usado: pipeline/baixar-faltantes-v3.js (ou o motor F5 colado direto na aba).

  • subidor.py antigo: aposentar ou corrigir a regra de assinatura antes de qualquer uso futuro — do jeito que está, apaga TIFF.

  • Rebaixar os 69 que sobraram (achados 2.25–2.27; 6 recuperados em 13/09 à noite) quando a origem se recuperar — todos têm thumbnail, falta a preservação. Um FALTA3.retomar()/F5.iniciar() na aba basta; o que já baixou fica marcado no localStorage (bras_faltantes_v3).

  • Handle 8396 sem imagem e sem diagnóstico — não está em pipeline/faltantes-preservacao.jsonl nem nas exceções conhecidas do MANIFEST; apareceu ao cruzar catálogo × miniaturas. Precisa de uma tentativa avulsa pra saber se é recusa da origem (achado 2.18) ou outra coisa.

  • .git do rafapolo/xyz está em 2,5 GB e cada publicação do atlas soma ~51 MB permanentes (binário não faz delta). Se o mural for republicar atlas com frequência, tirar os .webp do git — LFS, ou servir do próprio bucket, que já tem os thumbs em derivados/thumb256/.

  • Crawl da Fase 1 pode ter mais buracos (achado 2.10 do PLANO.md): 2 itens reais (1799, 4798) foram achados faltando no catálogo só porque o WordPress os citava — já corrigidos, mas não existe verificação exaustiva de que a enumeração por browse?type=title pegou 100% de cada comunidade. Sem ideia barata de auditar isso por completo agora.

  • Baixar os 348 legados recuperáveis pelo endereço novo (achado 2.28) — atualizar antes o baixador.py para https://objdigital.bn.br/objdigital2/acervo_digital/.

  • Decidir o que fazer com as 66 páginas HTML de visualizador (25 já em legado/ + 41 no endereço novo; achado 2.28): apagar do bucket e do ledger (não são imagem), ou montar a imagem a partir dos tiles Deep Zoom quando o objdigital voltar. Todos os 25 itens já têm preservação.

Ao final (não fazer antes do download terminar)

  • Gerar MANIFEST.md (o que é, como foi coletado, quando) — feito; atualizado em 2026-09-12 com derivados/ e os achados 2.19/2.20/3.3
  • Confirmar no bucket que a contagem de itens/checksums bate com os 13.058 itens reais esperados

Notas técnicas úteis pra retomar

  • Downloads automáticos do Chrome exigem permissão manual em chrome://settings/content/automaticDownloads (site adicionado: https://brasilianafotografica.bn.gov.br) — já concedida, vale pra novos scripts também, não só o original.
  • BRAS, BAIXA, BAIXA_THUMB, BAIXA_EXTRA — todos no window da mesma aba, mesmo IndexedDB (bras). Todos sobrevivem a fechar/reabrir a aba (dado fica salvo), mas o loop para e precisa recolar o script certo pra retomar.
  • Regerar as miniaturas derivadas (tudo reconstruível, nada precisa ser guardado): no finland, apt install libvips-tools parallel + s5cmd; pra cada chave de itens/*/preservacao/*, s5cmd cat num tmp e vipsthumbnail "$TMP" --size 256 --output "$OUT[Q=80,strip]", com parallel -j 10. ~640 thumbs/min, tráfego intra-Hetzner (bucket é hel1, o finland também) — não passa pelo link de casa. O que ainda está só em ~/Downloads sai mais barato derivado localmente com magick: mandar os 71 GB pro finland custaria os mesmos dias do upload pendente.
  • Cuidado ao parsear s5cmd ls: nomes de arquivo do acervo têm espaço duplo (48094 Álbum 311 - ...jpg). awk '{$1="";$2="";$3=""; print}' recolapsa esses espaços e produz chaves que não existem — 31 downloads falharam assim antes de eu perceber. Use sed -E 's|^[0-9/]+ +[0-9:]+ +[0-9]+ ||'`, que preserva o resto literal.
  • sqlite3 desta máquina tem .sqliterc forçando modo box: qualquer script que leia a saída precisa de sqlite3 -init /dev/null -noheader -separator $'\t', senão vem borda de tabela no lugar dos dados (me custou um diff inteiro de falso positivo).
  • until ! pgrep -f X; do sleep; done nunca termina: a linha de comando do próprio loop contém X, então o pgrep -f se acha e a condição nunca vira falsa. Um loop desses ficou girando horas depois do trabalho acabar (12/09, morto à mão). Esperar por PID (while kill -0 $PID) ou usar pgrep -f '[t]humb-local' para o padrão não casar consigo mesmo.
  • Parar processo lançado em background: SIGTERM, não SIGINT. Processo iniciado com & num shell não interativo herda o SIGINT ignorado (o subidor.py ficou 60 s vivo depois do kill -INT). Com SIGTERM, o aws s3 cp filho termina órfão, o que é inofensivo: sem linha no ledger e sem apagar o local, o próximo uploader sobe de novo a mesma chave.
  • Upload de casa: 69 Mbps (networkQuality -s, 12/09); download 312 Mbps.
  • Assinaturas aceitas como imagem (subidor-paralelo.py): JPEG FFD8, PNG, TIFF II*\0/MM\0*, GIF, JP2, BMP BM, WebP. Content-Type sai do conteúdo, não da extensão (TIFF chega com nome .jpg).
  • vipsthumbnail --output é relativo à pasta do arquivo de entrada, não ao diretório corrente: vipsthumbnail tmp/x.img --output out/y.webp tenta gravar em tmp/out/y.webp e falha. Usar caminho absoluto na saída. (Custou uma miniatura "refeita" que não foi refeita, em 13/09.)
  • Scripts-fonte em pipeline/: coletor.js / coletor-resume.js (metadados), baixador-navegador.js (preservação, todos os itens), baixador-thumb.js (miniaturas), baixador-extras.js (2a+ imagem de itens multi-bitstream), reparo-multibitstream.js (enumera bitstreams reais de um item).