# Ensaio local de rollback de imagem Ensaio descartável feito em 17 de outubro de 2026, nesta máquina, no projeto Docker `adaptador-rollback-ensaio`. Não é o rollback da homologação nem o da produção da Elo Pro. O gate F11 do [checklist](21-checklist-validacao.md) continua **pendente**. Ele exige duas imagens publicadas e o ambiente `hml-ada.elopro.app`. O procedimento de lá está na seção 10 de [20-homologacao-runbook.md](20-homologacao-runbook.md). Este documento não o aprova. ## O que foi comparado | | Atual | Anterior | | --- | --- | --- | | Commit | `bd0ecabf41330d8e48dbe070cb090b25fd6eb64c` | `2be97ea9694eb3fe44e125fbd2ab3e13d8cc488a` | | Tag da imagem | `adaptador-planilhas:bd0ecab` | `adaptador-planilhas:2be97ea` | | Id da imagem | `sha256:bc06e875c96b2a1f28b406fd0512e40b37399550355f204d32d77ae766db9eed` | `sha256:edc36b8532b172ea940ed6dd252e2f965a91694366adb5828e87f7c72dc520d9` | `git diff 2be97ea bd0ecab -- src migrations Dockerfile docker docker-compose.yml` não tem diferença. O commit atual só acrescenta o smoke local, o `package.json`, o `tsconfig.json` e a documentação. As quatro migrations (`0001` a `0004`) são as mesmas. Por isso a imagem anterior convive com o schema aplicado pela imagem atual. Não houve downgrade de migration. A tag `adaptador-planilhas:local` do compose principal não foi reescrita. Antes e depois do ensaio ela continuou `sha256:1efe0dfab0a80518563bd8ac2905466eca5692c05a26818a397551fffc05f1fa`, com web, worker, Postgres e Redis principais saudáveis. O banco `adaptador` seguiu com 1 modelo e as flags `spreadsheet_adapter` Alpha ligada e Beta desligada. O comando `flag` não foi executado. ## Isolamento Arquivo: `docker-compose.rollback-local.yml`. | Recurso | Nome | Publicado no host | | --- | --- | --- | | Projeto | `adaptador-rollback-ensaio` | não | | Rede | `adaptador-rollback-ensaio` | não | | Volume Postgres | `adaptador-rollback-ensaio-postgres` | não | | Volume storage | `adaptador-rollback-ensaio-storage` | não | | Banco | `adaptador_rollback` | não | | Redis | interno | não | | Web | `127.0.0.1:3310` | só loopback | Não há `build:` nesse compose. Sem `ADAPTER_IMAGE` ele recusa subir, para não cair na tag `:local`. Postgres e Redis não têm porta no host. A sonda `scripts/rollback-local-probe.ts` só aceita `http://127.0.0.1:3310`. Os volumes `adaptador-planilhas-local-postgres` e `adaptador-planilhas-local-storage` não entraram no ensaio. Não houve `docker compose down -v` no projeto principal, nem `git reset`. ## Comandos executados Conferência de compatibilidade, sem trocar de branch: ```bash git diff --stat 2be97ea bd0ecab -- src migrations Dockerfile docker docker-compose.yml git worktree add --detach "$TEMP/adaptador-2be97ea-src" 2be97ea ``` Imagens, sem mover a tag `:local`: ```bash docker build -t adaptador-planilhas:bd0ecab --build-arg SENTRY_RELEASE=bd0ecabf41330d8e48dbe070cb090b25fd6eb64c --target runner . docker build -t adaptador-planilhas:2be97ea --build-arg SENTRY_RELEASE=2be97eaf --target runner "$TEMP/adaptador-2be97ea-src" ``` Subida do ensaio e migration única, com a imagem atual: ```bash export ADAPTER_IMAGE=adaptador-planilhas:bd0ecab docker compose -p adaptador-rollback-ensaio -f docker-compose.rollback-local.yml up -d postgres redis storage-init docker compose -p adaptador-rollback-ensaio -f docker-compose.rollback-local.yml run --rm --no-deps web migrate docker compose -p adaptador-rollback-ensaio -f docker-compose.rollback-local.yml up -d web worker ``` A migration só rodou depois do Postgres saudável. Aplicou `0001_platform_shim.sql`, `0002_spreadsheet_adapter.sql`, `0003_rls.sql` e `0004_audit_append_only.sql`. Não existe passo de down. `0003` e `0004` continuam sem caminho de volta, como no runbook. Jornada fictícia: ```bash node --import=tsx scripts/rollback-local-probe.ts seed ``` Troca só de web e worker, volumes intactos: ```bash docker compose -p adaptador-rollback-ensaio -f docker-compose.rollback-local.yml stop -t 30 web worker export ADAPTER_IMAGE=adaptador-planilhas:2be97ea docker compose -p adaptador-rollback-ensaio -f docker-compose.rollback-local.yml up -d --no-deps --force-recreate web worker ``` Volta para a imagem atual, do mesmo jeito, com `ADAPTER_IMAGE=adaptador-planilhas:bd0ecab`. Depois das evidências, só o projeto do ensaio foi removido: ```bash docker compose -p adaptador-rollback-ensaio -f docker-compose.rollback-local.yml down -v git worktree remove --force "$TEMP/adaptador-2be97ea-src" ``` Antes do `down -v`, os nomes listados eram apenas `adaptador-rollback-ensaio`, `adaptador-rollback-ensaio-postgres` e `adaptador-rollback-ensaio-storage`. As imagens `:bd0ecab` e `:2be97ea` foram mantidas. A tag `:local` não. ## Resultado Prontidão da imagem atual, antes da troca: HTTP 200, `status=ready`, `environment=development`, e `config`, `database`, `migrations`, `redis`, `storage` e `job_mode` em `ok`. Dados fictícios gravados: | Campo | Valor | | --- | --- | | Modelo | `52add4ba-88b5-4611-b8d7-4ffe33273d3e` | | Operação | `ee5fb92f-57ba-4414-a7ab-b9635e243aea` | | Fonte | `eabec970-e86d-4b39-8c76-c2b5d10b4674` | | SHA-256 do XLSX original | `74a4130f7712f94727c6bbf8708b9e77fd34e61c7504da8b84be309cbd6d9559` | | SHA-256 do XLSX exportado | `89abbca277ea2569bf63ac5cffcd581181b5b6e97d22a285cf21786e175608dc` | | SHA-256 do CSV exportado | `199e83c30d6fbae7174a4ecd563d32e9ece19a677209e79e88bb56116a6b8977` | O mesmo SHA-256 do original foi lido no volume pela imagem atual e, depois da troca, pela imagem `2be97ea`. O `content_sha256` no banco isolado coincidiu. `schema_migrations` permaneceu nas quatro migrations. As flags do banco isolado nasceram do login local de desenvolvimento (Alpha ligada, Beta desligada) e não mudaram na troca de imagem. Na imagem anterior e de novo na atual: - `GET /api/health/ready` voltou `ready`. - O modelo foi encontrado pelo id e pelo nome. - A operação permaneceu `completed`. - Os bytes armazenados de XLSX e CSV repetiram o SHA-256. - O PDF voltou HTTP 200. O SHA-256 do PDF não se repete: ele é gerado no download, não fica gravado. Isso ocorreu nas duas imagens, não é incompatibilidade de rollback. - O worker da imagem anterior registrou `[worker] pronto`. Depois da restauração, o healthcheck do worker ficou `healthy`. Tempo observado da troca, do `stop` até o `ready`, no ensaio local: cerca de 3 s na ida para `2be97ea` e 2,8 s na volta para `bd0ecab`. Esse número não descreve pull de registry, Nginx nem a flag do piloto. Nenhuma incompatibilidade de schema apareceu. O rollback não foi interrompido. ## Como repetir a recuperação 1. Anote a imagem que está no ar. No ensaio, o retorno é `adaptador-planilhas:bd0ecab`. 2. Pare só `web` e `worker` do projeto isolado, com `stop -t 30`. 3. Aponte `ADAPTER_IMAGE` para a tag do commit desejado. 4. Suba de novo com `up -d --no-deps --force-recreate web worker`. 5. Não rode migration de down. Se `migrations` não ficar `ok`, ou se o processo não subir, interrompa e volte para a tag atual pelo mesmo caminho. Schema incompatível é operação planejada, com backup, não rollback às pressas. 6. Confira `/api/health/ready`, o modelo, o SHA-256 do original no volume e o download do XLSX e do CSV. Na homologação real o equivalente está no runbook: tag `staging-`, `up -d --no-deps web worker`, e a flag do módulo é outro passo, feito com `flag`, antes da troca e só religada depois da saúde. Este ensaio não fez isso e não deve ser copiado para o servidor compartilhado. ## Limitações - As duas imagens executam o mesmo código de aplicação. O ensaio prova a troca de imagem com volume e schema preservados. Não prova um rollback atravessando uma migration nova. - Não usa Supabase, SSO, Sentry nem bucket da Elo Pro. - A flag do piloto não foi ligada nem desligada. - O PDF não serve como hash de integridade entre downloads. - O tempo de 3 s é só o recreate local das duas imagens já presentes no Docker.