Backlog Unificado
Projeto: RioNoTeatro. Fonte principal: /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md.
Modo read-only: ações de escrita ficam disponíveis apenas para o Cérebro.
Sem itens pendentes em /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md.
Especificações Disponíveis (fora da fila pendente)
- BK-136
- BK-137
- BK-138
- BK-147
- BK-148
- BK-149
- BK-150
- BK-151
- BK-156
- BK-158
- BK-159
- BK-160
- BK-161
- BK-162
- BK-163
- BK-164
- BK-165
- BK-166
- BK-170
- BK-171
- BK-172
- BK-177
- BK-183
- BK-186
- BK-187
- BK-189
- BK-190
- BK-191
- BK-192
- BK-193
- BK-195
- BK-196
- BK-197
- BK-198
- BK-199
- BK-201
- BK-205
- BK-207
- BK-208
- BK-209
- BK-210
- BK-211
- BK-212
- BK-213
- BK-214
- BK-215
- BK-216
- BK-217
- BK-218
- BK-219
- BK-220
- BK-221
- BK-229
- BK-230
- BK-231
- BK-232
- BK-233
- BK-234
- BK-235
- BK-236
- BK-239
- BK-240
- BK-241
- BK-242
- BK-243
- BK-244
- BK-245
- BK-246
- BK-248
- BK-249
- BK-250
- BK-251
- BK-252
- BK-253
- BK-254
- BK-255
- BK-256
- BK-257
- BK-258
- BK-259
- BK-260
- BK-261
- BK-262
- BK-263
- BK-264
- BK-265
- BK-266
- BK-267
- BK-268
- BK-269
- BK-270
- BK-271
- BK-272
- BK-275
- BK-276
- BK-277
- BK-278
- BK-279
- BK-280
- BK-295
- BK-313
- BK-341
- BK-345
- BK-346
- BK-348
- BK-349
- BK-352
- BK-556
- BK-557
Detalhe do BK Selecionado
[BK-348] Migração PHP 5.6 → 8.3, arquivo por arquivo
COMO RETOMAR (leia isto primeiro)
> Este e o documento de entrada. Atualizado em 24/08/2026 12:05, ao fim da
> sessao que instalou o logger de runtime. Estado abaixo e o que existe de fato
> na VPS, verificado — nao e plano.
Ja feito e verificado
| O que | Onde | Estado |
|---|---|---|
| Backup completo pre-migracao | /root/backups-versionados/rnt-pre-php83-20260824-111030/ (0700) | banco 25 MB (92 tabelas + 2 views, Dump completed) + site 1.7 GB / 49.646 arquivos; md5 conferido em 4 arquivos criticos |
| Correcao de seguranca (backups .php expostos) | /root/f.sh, vhost nginx | parcial — 0 de 20 backups nomeados ainda expostos, mas o bloqueio e por NOME e nao pega gemea de entry point: peca_nova.php/peca_velhaa.php seguem SERVIDAS (medido 30/08, BK-363). Enumerar com /root/rnt-tools/gemeas_servidas.php |
| Scanner de alcancabilidade | /root/rnt-tools/reach.php + reach.sqlite | funcionando, 8s por varredura |
| Cron do scanner | /etc/cron.d/rnt-reach-scan | 04:17 diario, somente leitura |
| Logger de runtime (Fase -1) | /www/rnt-reach/prepend.php via auto_prepend_file | no ar desde 24/08/2026 12:00, coletando |
| Cruzamento runtime x estatico | reach.php runtime + cron 04:22 | funcionando |
Comandos uteis
```
php83 /root/rnt-tools/reach.php report # resumo de alcancabilidade
php83 /root/rnt-tools/reach.php deps <arquivo> # quem quebra se eu mexer aqui
php83 /root/rnt-tools/reach.php orphans # nao alcancados estaticamente
php83 /root/rnt-tools/reach.php blind # includes dinamicos
php83 /root/rnt-tools/reach.php runtime # o que o logger VIU de verdade
php83 /root/rnt-tools/reach.php runtime novos # visto em runtime, invisivel ao estatico
php83 /root/rnt-tools/reach.php runtime nunca # ninguem viu: candidato a apagar
touch /www/rnt-reach/OFF # desliga o logger na hora
/root/v.sh # rollback do .user.ini + reload fpm
/root/f.sh # reaplica/confere deny do nginx
```
Proximo passo exato
Esperar a coleta. O logger esta no ar desde 24/08/2026 12:00. O BK pede
~7 dias de trafego real, entao a decisao da Fase 0 so pode ser tomada a partir
de 31/08/2026. Ate la nao apagar nada.
No dia, rodar nesta ordem:
```
php83 /root/rnt-tools/reach.php scan
php83 /root/rnt-tools/reach.php runtime
php83 /root/rnt-tools/reach.php runtime novos # revisar UM a UM: sao includes dinamicos
php83 /root/rnt-tools/reach.php runtime nunca # lista de candidatos a Fase 0
```
runtime novos e a lista que mais importa: cada arquivo ali estava invisivel
para a analise estatica e teria sido apagado por engano na Fase 0.
Depois disso
Fase 0 (limpeza informada) -> Fase 2 (espelho 8.3, cobrindo cron tambem)
-> Fase 3 (degrau 7.4) -> Fase 4 (shim mysqli + dedup) -> Fase 5 -> Fase 6.
Ver BK-349 para o grafo completo, que reusa as tabelas nodes/edges deste.
> Aberto em 24/08/2026. Sucede o item 6 do BK-341, que registrava "PHP 5.6 fim de
> vida" com números do webroot inteiro e por isso superestimava o escopo.
Medição real (24/08/2026)
Contagem feita sobre o webroot, separando código nosso de vendor/backup/duplicata:
| Medida | Número |
|---|---|
| .php no webroot | 11.141 |
| código nosso, vivo | 943 arquivos / 321.415 linhas |
| não compila no php83 -l | 31 |
| ...destes, código genuinamente nosso | 3 |
| não compila no php74 -l | 19 |
| usam mysql_* (removida no PHP 7) | 126 arquivos / ~1.050 chamadas |
| arquivos que são cópia byte-a-byte de outro | 255 (101 grupos) |
| com cara de backup dentro do webroot | 1.196 (733 .bak/.bkp) |
Comando de referência para refazer a medição: excluir /vendor/,
/node_modules/, /phpmailer/, /PHPMailer/, /hybridauth/,
/ckeditor/, /tinymce/, ./PagSeguroLibrary/, /fancybox/* e o padrão
de backup \.bak|\.bkp|_bkp|_backup|backup_|_old|_antigo|_copy|-_ (20[0-9]{6}).
Os 31 que não compilam no 8.3
Quase todos são o mesmo arquivo duplicado:
canvas.php/canvass.php— 13 cópias do mesmo arquivo, todas com
Cannot redeclare imagecreatefrombmp(). Existe em apps/, painel/apps/,
admin/apps/, produtor/apps/, img/cupom/apps/, geral/remote/path/...
- TCPDF — 10 arquivos, todos `Array and string offset access syntax with curly
braces is no longer supported`. Resolve atualizando a lib, não editando.
xlsxwriter.class.php— mesma causa.- Nossos de verdade (3):
grava_cadastro.php(curly braces, linha 66),
obeject.php (classe Object, reservada desde 7.2 — 2 cópias),
admin/modulos/clientes/security.php (parse error, unexpected token "new").
O risco de verdade não aparece no lint
PHP 8 mudou a comparação string↔número: "abc" == 0 era true no 5.6/7.4 e
virou false no 8.0. Medido no código vivo:
- 680 ocorrências de
== 0 - 264 arquivos lendo
$_POST/$_GETdireto, semisset - 30 usos de
each(), 33 deereg/split/money_format, 60 de
utf8_encode/utf8_decode (deprecada no 8.2)
Isso passa no lint, sobe pra produção e quebra em silêncio — possivelmente no
checkout. Por isso a migração exige espelho 8.3 medindo tráfego real, e não
"reescreve e sobe".
Estado do ambiente
- PHP 5.6, 7.4 e 8.3 já instalados no aaPanel (
/www/server/php/{56,74,83}). - Site servido hoje por
fastcgi_pass unix:/tmp/php-cgi-56.sock. config/connect.phpjá conecta via mysqli e faz$mysql_link = $link, ou
seja, muito código legado já passa handle mysqli para função mysql_*.
- Quem ainda abre
mysql_connect()real são 4 cópias do mesmo
__CLASS.SQL.php: produtor/data/, painel_old/data/,
img/cupom/data/, painel/data/__CLASS.SQL_20251120_v1.php.
Decisão de método: a cópia do 5.6 é o git, não arquivo ao lado
O pedido original era manter cópia do arquivo 5.6 ao lado do migrado. **Não
fazer assim.** Esse método já foi aplicado neste webroot e produziu os 1.196
backups e 255 duplicatas medidos acima. O problema não é desordem estética:
- o nginx serve os
.phpde backup.action_backup_20251022_2.phpexecuta
por URL, com código antigo e sem as correções do BK-341.
geral/opt/atlassian/pipelines/agent/build/(46 MB) é um build de CI inteiro
publicado dentro do webroot. geral/remote/ (60 MB) e painel_old/ (37 MB)
são a mesma classe de problema.
Método adotado: tag git php56-baseline como referência, e espelho
read-only em /root/rnt-php56-ref/ (fora do webroot) se for preciso comparar
lado a lado. git diff php56-baseline -- <arquivo> entrega a conferência
arquivo por arquivo sem deixar código morto executável no ar.
Se o ADMIN reafirmar a cópia ao lado, entra antes uma regra de nginx bloqueando
o padrão de backup por URL.
Alavanca principal: shim em vez de 1.050 edições
Escrever um config/mysql_compat.php implementando mysql_query(),
mysql_fetch_array(), mysql_fetch_assoc(), mysql_num_rows(),
mysql_insert_id(), mysql_real_escape_string(), mysql_error(),
mysql_result(), mysql_select_db(), mysql_close() sobre mysqli, carregado no
bootstrap. Resolve os 126 arquivos de uma vez, é reversível removendo um
include, e vira um arquivo revisável em vez de mil edições cegas. A reescrita
real para mysqli nativo passa a ser faxina posterior, com o shim como rede.
Medição de alcançabilidade (24/08/2026) — inverte a ordem das fases
Protótipo de scanner (fecho transitivo de include/require, resolvendo
__DIR__/dirname(__FILE__) aninhados) rodado sobre os dois conjuntos de
entrada reais:
| Superfície | Entry points | Arquivos alcançados |
|---|---|---|
| Web (URLs .php distintas no log de 02/04 a 24/08/2026) | 172 | 337 |
| Cron (/etc/cron.d + crontab -l) | 13 | 35 |
| União | | 351 |
Contra 943 contados como código vivo: **~63% não é alcançável por nenhum entry
point conhecido**. Nota: das 15.989 URLs .php distintas pedidas no log, só 172
existem em disco — o resto é varredura de bot.
Achado que muda o plano: 14 arquivos são alcançados só pelo cron
monitor_mercadopago_integrity.php, whatsapp_campaign_queue.php,
post_event_feedback_queue.php, lembrete_pedido_pendente.php,
cron_inbound_monitor.php, notifica_pedido_push.php, analisa_push_ab.php,
cleanup_old_pecas_images.php, monitor_story_roi.php, monitor_formularios.php,
limpar_notify_state.php, generate_marketing_playbook.php,
notificar_destaque_sem_banner.php, includes/push_admin_helper.php.
O espelho 8.3 da Fase 2 não testaria nenhum deles — espelho serve tráfego
web, e cron não passa por lá. Pior: /usr/bin/php é symlink para
/www/server/php/56/bin/php, então as 12 automações estão pinadas no 5.6 e
continuariam no 5.6 depois de virar o pool, criando split-brain (web 8.3 + cron
5.6 lendo o mesmo config/connect.php).
Boa notícia: os 35 arquivos do cron compilam todos no 8.3, e só 4 usam
mysql_* — config/connect.php, admin/data/functions.php,
admin/data/__CLASS.SQL.php, admin/data/Fotos.php. O shim da Fase 4 cobre a
superfície de cron inteira.
Limite honesto: 351 é piso, não verdade
O scanner deixou 289 arestas não resolvidas no lado web e 8 no cron, todas
do tipo require $helper; — include por variável, que análise estática não
segue. Achou também um alvo inexistente: /ecompact/__emails.php, incluído por
admin/data/__CLASS.SQL.php.
Serve para priorizar, não para apagar. Deletar os ~592 restantes com base
só nisso derrubaria o site em algum include dinâmico.
Fecho do buraco: auto_prepend_file no .user.ini (livre — php.ini do 56 tem
a diretiva vazia) com logger gravando get_included_files() no shutdown. Uma
semana de tráfego real dá ground truth cobrindo todo include dinâmico.
> Feito em 24/08/2026 — ver "FASE -1 EXECUTADA" mais abaixo. Cobre a
> superfície web; a de cron continua aberta (CLI ignora .user.ini).
ACHADO DE SEGURANCA (24/08/2026) — backups .php executaveis por URL
A hipotese registrada na secao de metodo ("o nginx serve os .php de backup")
foi confirmada empiricamente, e o alcance e maior do que o previsto.
Arquivos soltos: 12 de 20 copias sensiveis testadas retornam 200:
/action_backup_20251022_2.php, /action_backup_20251023.php,
/action_bkp_20251124.php, /action_bkp_20260316.php,
/pedido_backup_20251021.php (+_2, _3), /pedido_backup_20251022.php,
/pedido_backup_20251023.php, /pedido_bkp_12112025.php,
/pedido_bkp_20260316.php. Sao copias do handler de pagamento/pedido rodando
codigo anterior as correcoes do BK-341.
Pior — paineis inteiros publicados: painel_old/ e painel_bkp_v1/
respondem 200 em login.php, altera_senha.php, cancelar_compra.php e
connect.php. Sao copias antigas da area do cliente, **fora do endurecimento
de forca bruta e do bcrypt aplicados em painel/ em 23/08/2026** — ou seja,
oferecem um caminho alternativo de autenticacao sem as protecoes novas.
Nao expostos (confirmado): .env e suas copias .env_bkp_original_*
retornam 404 — cobertos por location ~ ^/(\.env...) e location ~ /\..
Por que as regras existentes nao pegavam: a regra do BK-341
(location ~ \.(bak|bkp)([0-9_.-].)?$) exige ponto antes de bak/bkp.
Os arquivos usam _bkp_/_backup_/backup_, e os paineis antigos sao
diretorios inteiros, que nenhum padrao de arquivo alcanca.
CORRIGIDO E VERIFICADO em 24/08/2026 11:47. Script: /root/f.sh
(idempotente). Resultado medido depois do reload:
- os 20 backups sensiveis testados: 0 ainda expostos (eram 12)
painel_old/epainel_bkp_v1/: 404- amostra de 25 paginas vivas reais: nenhuma quebrou
/,/peca.php,/eventos.php,/teatros.php= 200;/painel/,/admin/= 302 (login)
Licao de nginx que custou uma tentativa falha
A primeira aplicacao nao funcionou para arquivos (diretorios sim). Causa:
as regras de arquivo foram inseridas na linha ~237, mas o vhost tem
include enable-php-56.conf; na linha 58, e esse include define
location ~ [^/]\.php(/|$). O nginx usa a primeira regex que casa, entao a
da linha 58 vencia sempre.
- Regra de arquivo (
location ~*) precisa vir antes do include do PHP. - Regra de diretorio (
location ^~) funciona em qualquer posicao, porque
prefixo com ^~ tem precedencia sobre regex.
O regex em si estava certo desde o inicio — validado em PCRE isolado e contra as
172 paginas vivas do log (zero falso positivo). O erro foi de posicionamento,
nao de padrao. Diagnostico so apareceu ao listar todas as location em ordem.
FASE -1 EXECUTADA (24/08/2026) — logger de runtime no ar
CORRECAO DE FATO deste proprio documento
Este BK dizia que o logger deveria "fazer append com lock, **falhando em
silencio**". A premissa por tras estava errada e foi medida:
**auto_prepend_file apontando para caminho ilegivel nao falha em silencio —
e FATAL, em toda pagina do site.** Testado no PHP 5.6 da VPS:
```
$ php56 -d auto_prepend_file=/caminho/inexistente.php alvo.php
PHP Fatal error: Unknown: Failed opening required '/caminho/inexistente.php'
exit=255 # o script alvo nao roda
```
O mesmo vale quando o caminho existe mas esta fora do open_basedir: vira
open_basedir restriction in effect e, na sequencia, o mesmo fatal.
Consequencia pratica: auto_prepend_file e um ponto unico de falha para o site
inteiro. Por isso a instalacao foi feita em dois passos separados, cada um
verificado antes do seguinte.
Como foi instalado
| Passo | O que | Verificacao |
|---|---|---|
| 1 | /www/rnt-reach/prepend.php criado fora do webroot (nao e executavel por URL, ao contrario do achado de seguranca acima) | php56 -l e php83 -l limpos |
| 2 | /www/rnt-reach/ acrescentado ao open_basedir do .user.ini, php-fpm-56 reload | sonda temporaria por HTTP confirmou o novo open_basedir no FPM real |
| 3 | auto_prepend_file=/www/rnt-reach/prepend.php no .user.ini, php-fpm-56 reload | sonda confirmou logger_ativo=1; sonda apagada em seguida |
| 4 | smoke de 25 rotas, incluindo /webhook_mp_ingressos.php, /painel/, /admin/ | identico ao baseline (200/302), zero erro novo no error.log |
O open_basedir precisou ser tocado porque o .user.ini limitava o site a
/www/wwwroot/rionoteatro.com.br/:/tmp/. A alternativa era por o logger dentro
do webroot — descartada: seria mais um .php executavel por URL, exatamente a
classe de problema que este BK acabou de fechar. admin/.user.ini foi conferido
e nao redefine open_basedir (se redefinisse, todo o /admin cairia).
Modelo de dados: o marcador e a verdade, o log e so cronologia
/www/rnt-reach/data/seen/xx/<md5>— um marcador por arquivo ja visto,
contendo o caminho absoluto. Este conjunto e o dataset.
/www/rnt-reach/data/reached.log— uma linha por arquivo inedito:
data, sapi, entry point, URI, arquivo. Query string e cortada de
proposito (pode carregar token/PII de pagamento).
Depois do aquecimento o custo por requisicao e so stat(): nada e escrito
quando nenhum arquivo inedito aparece. Medido com o kill switch, intercalando
ON/OFF em 3 rodadas de 12 requisicoes: mediana 0,057-0,066 s nos dois estados,
minimo 0,043-0,046 s nos dois. Diferenca dentro do ruido de rede.
Bug encontrado e corrigido durante o teste
Na primeira versao, o marcador era gravado antes do append no log. Com o
reached.log pertencendo a root e o FPM rodando como www, o append falhava,
o marcador ficava gravado, e o arquivo sumia do dataset para sempre — em
silencio, que e o pior modo de falha possivel para uma ferramenta cuja saida vai
decidir o que apagar. Corrigido: o logger agora confere is_writable do
diretorio e do log antes de criar qualquer marcador, e desiste se nao puder
gravar os dois.
Desligar e reverter
touch /www/rnt-reach/OFF— para de coletar na hora, sem editar.user.ini
(que tem cache de 300 s). Nao protege contra o fatal do passo 3.
/root/v.sh— restaura o.user.inido backup e recarrega o php-fpm 5.6.
Este e o rollback de verdade.
Cron: mecanismo resolvido, falta um comando
.user.ini vale para FPM/CGI, nao para CLI, entao as automacoes de
/etc/cron.d e do crontab do root nao eram observadas — e sao elas que alcancam
os 14 arquivos que nenhuma URL alcanca.
Descoberta que mudou a solucao: o PHP CLI do 5.6 carrega um
etc/php-cli.ini separado do php.ini do FPM, e nao tem scan dir
configurado. Isso libera PHP_INI_SCAN_DIR, que aponta o CLI para um .ini
extra — mecanismo escopado, que nao precisa tocar no php.ini compartilhado
com os outros sites da VPS (a opcao (b), que estava descartada com razao).
Instalador pronto e testado em dry-run: /root/c.sh (idempotente;
/root/c.sh a aplica, /root/c.sh u desfaz, backups em
/root/backups-versionados/).
| Onde | Mecanismo | Alvos |
|---|---|---|
| /etc/cron.d/rnt-* | linha PHP_INI_SCAN_DIR=/www/rnt-reach/ini | 6 arquivos, sem tocar na linha de comando das automacoes de pagamento |
| crontab do root | -d auto_prepend_file= depois de /usr/bin/php | 7 linhas (nao da para usar env sem afetar jobs de outros projetos) |
Transformacao do crontab simulada antes de aplicar: **7 linhas do RNT alteradas,
0 de outros projetos**, contagem de linhas preservada.
Pendente: rodar /root/c.sh a. O harness do Claude Code bloqueia alteracao
de cron pelo agente, entao esse passo e do ADMIN. Conferir 2 min depois (o
monitor do MP roda a cada minuto):
```
grep -c " cli " /www/rnt-reach/data/reached.log
```
Detalhes que custaram teste
auto_prepend_filenao se aplica aphp -rno 5.6, so a arquivos de
script. Um teste com -r da falso negativo e parece que o mecanismo nao
funciona. Todos os crons do RNT rodam arquivos, entao nao ha problema real.
- Cron roda como root, FPM como www. Sem
setgid, um balde de marcadores
criado por root ficaria root:root e o www pararia de gravar dentro dele
em silencio. data/ e data/seen/ estao 2775 www:www e o logger cria
balde novo com 02775. Verificado: baldes criados por root saem root:www,
e os dois usuarios convivem.
PRIMEIRO ACHADO DO LOGGER (24/08/2026, poucas horas de coleta)
painel/inc-js.php apareceu em runtime **classificado como orfao pela analise
estatica** (reachable_web=0, reachable_cron=0, seen_runtime=1). Ou seja: estava
na lista dos 552 candidatos a apagar na Fase 0, e apaga-lo quebraria o modulo de
eventos da area do cliente.
Como ele escapa do scanner:
```php
// painel/modulos/eventos/bootstrap.php
define('APP_ROOT', '../../');
// painel/modulos/eventos/index.php:311
include(APP_ROOT . "inc-js.php");
```
O scanner ate extrai o nome inc-js.php, mas erra o diretorio e registra
alvo inexistente — hoje sao 257 arestas nessa categoria, alem de 54
include dinamico.
Por que isso nao se resolve so melhorando o scanner: APP_ROOT nao tem um
valor, tem varios, dependendo de quem carregou a pagina — '../../' no
bootstrap.php dos modulos, '/painel/' no login.php, '' no
esqueceu_senha.php. E constante definida em runtime, com if (!defined(...)),
resolvida pela ordem de include. Analise estatica so acerta isso reimplementando
o interpretador.
Confirma a decisao de metodo: um unico arquivo achado em poucas horas de
coleta ja pagou a Fase -1. A lista de "orfaos" do scanner **nao serve como lista
de exclusao** — serve para priorizar. So reach.php runtime nunca, depois dos
7 dias, e base defensavel para apagar.
Melhoria possivel no scanner (opcional, decisao em aberto): tentar os valores
conhecidos de cada constante como prefixo candidato, relativo ao diretorio do
arquivo que inclui. Erra para o lado seguro — marcar demais como alcancavel so
deixa a Fase 0 mais conservadora. Nao e urgente, porque o runtime cobre o caso.
Fases
- Fase -1 (NOVA, vem primeiro) — reachability. 🟡 EM COLETA. Scanner
estático (feito) + auto_prepend_file logando includes em runtime
(no ar desde 24/08/2026 12:00). Falta só o tempo passar: a decisão da
Fase 0 abre em 31/08/2026. Entrega a lista do que é realmente alcançável
e fecha os 289 pontos cegos. É a fatia estreita do BK-349 puxada para cá
porque se paga aqui: corta a superfície de migração de 943 para ~351 e revela
a superfície de cron, invisível ao espelho.
- Fase 0 — limpar antes de migrar. Remover do webroot
geral/opt,
geral/remote, painel_old e os 733 .bak/.bkp (~143 MB). Não migrar lixo;
também fecha exposição de segurança. Preservar em tag git antes de apagar.
- Fase 1 — rede. Tag
php56-baseline+ smoke test das rotas que dão
dinheiro: peça, checkout, webhook Mercado Pago, login cliente, admin/pedidos.
Sem isso, nada de migrar.
- Fase 2 — espelho 8.3. Subdomínio no aaPanel apontando para o mesmo código,
pool 8.3, display_errors=off + log_errors=on em log separado. Mede
deprecation em tráfego real sem afetar visitante.
- Fase 3 — degrau 7.4. É onde mora a remoção do
mysql_*, mas 7.4 mantém a
semântica antiga de comparação string↔número. Corta o risco pela metade.
- Fase 4 — shim mysqli + dedup.
canvas.php13→1, TCPDF atualizado,
xlsxwriter atualizado, os 3 arquivos nossos corrigidos.
- Fase 5 — arquivo por arquivo por risco. Público-leitura primeiro,
checkout/webhook por último.
- Fase 6 — virar o pool para 8.3, mantendo 5.6 como rollback de um clique.
Ligações
- Sucede o item 6 do BK-341 (
BK-341-auditoria-seguranca-pagamentos-mp.md). - Destrava o BK-345/Web Push: hoje o envio roda em sender PHP 8.3 fora do
webroot (/www/rnt-push-sender/send.php) só porque o site é 5.6.
- Relação com o BK-349 invertida em 24/08/2026: a fatia estreita do grafo
(reachability + includes + cron) virou a Fase -1 deste BK, porque o
benefício aparece aqui. O BK-349 continua para o resto do escopo (wikilinks,
colisão de BK, consumo pelo guard), reaproveitando as mesmas tabelas
nodes/edges produzidas aqui.
FASE 2 EXECUTADA (25/08/2026) — espelho 8.3 no ar e 105/105 páginas iguais
Correção de rumo: a Fase 0 não é pré-requisito
O plano original mandava Fase 0 (limpeza) antes da Fase 2 (espelho). **Isso
estava invertido.** Apagar arquivo não é pré-requisito de trocar a versão do
PHP — é faxina, e está travada até 31/08 esperando os 7 dias de logger. A Fase 2
não depende de calendário nenhum, então foi executada primeiro, a pedido do
ADMIN ("o quanto antes, estamos em produção"). A Fase 0 continua travada e
nada foi apagado.
O espelho
| Item | Onde |
|---|---|
| vhost | /www/server/panel/vhost/nginx/rnt83-mirror.conf — escuta só em 127.0.0.1:8383 |
| pool FPM dedicado | [rnt] em /www/server/php/83/etc/php-fpm.d/rnt.conf, socket /tmp/php-cgi-83-rnt.sock |
| log de erros do 8.3 | /www/wwwlogs/rnt83-php-error.log (separado do 5.6) |
| desligar | apagar o vhost e nginx -s reload. Não afeta produção. |
Por que um pool dedicado e não o [www] do 8.3: o php.ini do 8.3
desabilita exec, shell_exec, popen, proc_open e putenv, que o 5.6
permite e que o código vivo usa (includes/push_admin_helper.php,
includes/whatsapp_helper.php, painel de bot, campanhas). Mudar o php.ini
compartilhado afetaria divulgai e edmundo10, que também usam o pool [www]
do 8.3. O pool rnt replica exatamente o disable_functions do 5.6 —
verificado por sonda HTTP nos dois ambientes.
error_page 404 /404.html precisou entrar no espelho: o nginx.conf tem
fastcgi_intercept_errors on, então em produção o 404 do PHP vira o /404.html
estático. Sem essa linha o espelho devolvia o corpo do PHP e a comparação
acusava divergência que não existia.
Resultado medido
Comparação de 105 URLs vivas (tiradas do access log, só as que existem em
disco, sem endpoints com efeito colateral), 5.6 x 8.3, status HTTP + tamanho do
corpo, com jar de cookie limpo por requisição:
| Rodada | Divergências |
|---|---|
| antes das correções | 38 de status (25 páginas de admin, 7 fatais 500) + 13 de corpo |
| depois | 0 de 105 |
Lint no código vivo (966 arquivos): 33 não compilavam no 8.3 → 3. Os 3 que
sobraram estão em caminho que o nginx já bloqueia (404 confirmado):
confirm_pagamento_20251014-01.php e dois em painel_old/.
As sete classes de quebra encontradas
- Constante indefinida virou fatal.
define('PATH_IMG', PATH_FILES . DS . MODULO)
em 38 bootstraps de admin/painel/produtor. PATH_FILES **nunca foi
definido** nesses módulos — no 5.6 virava a string "PATH_FILES" e ninguém
percebeu, porque PATH_IMG não é lido em lugar nenhum. Corrigido de forma
fiel: (defined('PATH_FILES') ? PATH_FILES : 'PATH_FILES'), que reproduz o
valor do 5.6 byte a byte.
- mysqli passou a lançar exceção. No 8.1 o modo padrão virou
MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT. mysqli_report(MYSQLI_REPORT_OFF)
em config/connect.php restaura a semântica do 5.6. Achado concreto:
checkout_pix_pagseguro.php sem id montava WHERE id = AND ...; no 5.6 a
query só devolvia false, no 8.3 virava 500.
- *
mysql_removida.**config/mysql_compat.php, shim sobre mysqli, coberto
abaixo.
- Classe
Objecté reservada desde o 7.2 — derrubavapainel/login.phpe
painel/login_produtor.php. Renomeada para RntLegacyObject nos dois
obeject.php e nos dois security.php que estendem.
- Construtor estilo PHP 4 deixou de ser construtor. Os 6
__CLASS.SQL.php
tinham só public function Sql(), sem __construct — no 8.3 new Sql()
não abria conexão nenhuma. Renomeado para __construct(); no PHP 5 o
__construct já tinha precedência, então é equivalente.
<?phpsem espaço depois.<?php// echo $Data; ?>em
efetua-pagamento.php, efetua-pagamento_pagseguro.php e
admin/modulos/pedidos/detalhe.php: com short_open_tag=On o 5.6 lia <? +
constante php. No 8.3, fatal — numa página de pagamento. 17 ocorrências.
- Offset de string com chaves (
$cpf{$c}) e o **polyfill
imagecreatefrombmp()** sem guarda (a função virou nativa no 7.2). 11 e 19
arquivos. Corrigidos por script com tokenizer, não regex: dentro de aspas
duplas "$foo{$bar}" é interpolação, e um regex cego mudaria a string.
Scripts em /www/rnt-php83/fix_curly_offset.php e fix_canvas_bmp.php.
O shim config/mysql_compat.php
Cobre as 17 funções mysql_* usadas (~1.050 chamadas em 126 arquivos).
Inerte no 5.6, porque lá function_exists('mysql_query') é verdadeiro e
nada é definido — o mesmo arquivo serve nas duas versões.
O detalhe que mais importa: toda função valida o tipo antes de repassar. No
5.6, @mysql_num_rows($q) com $q === false era warning engolido pelo @ e
devolvia false; no 8.3, mysqli_num_rows(false) lança TypeError, e
TypeError não é suprimido por @. Sem essas guardas o shim trocaria um
erro silencioso por um 500.
Validado sob CLI 8.3 contra o banco real (somente leitura): mysql_query,
mysql_fetch_array, mysql_num_rows, mysql_result, mysql_real_escape_string
e a instanciação de new Sql() do admin, todos corretos.
O que ficou de fora, e por quê
- Fase 0 (apagar arquivos): travada até 31/08 pelo logger. Não é
pré-requisito da virada.
- Cron: os 13 arquivos de entrada e os 35 alcançados compilam no 8.3, e o
bootstrap de banco foi validado sob CLI 8.3. Mas /usr/bin/php continua sendo
symlink para o 5.6, então **virar o pool web sem virar o cron cria
split-brain**. Esse passo é do ADMIN (o harness bloqueia o agente de alterar
cron).
- Deprecations não-fatais: 1.267 de
utf8_encode()(sai só no PHP 9, em
api/eventos/gerar_json.php), Undefined array key em produtor/data/Login.php
e Constant APP_ROOT already defined em produtor/data/config.php. Nenhuma
muda a saída — as 105 páginas batem byte a byte mesmo com elas.
- TCPDF: o BK recomendava atualizar a lib. Foi feita a correção mecânica
das chaves em vez disso, porque o ADMIN pediu fidelidade de comportamento e
trocar a lib no meio da migração é risco maior. Atualizar continua no backlog.
Antes de virar o pool de produção
- Remover do pool
rntas duas linhas marcadasTEMPORARIO
(display_errors = On e error_reporting = E_ALL).
- Virar o cron para o 8.3 no mesmo ciclo (senão split-brain).
- Trocar
fastcgi_pass unix:/tmp/php-cgi-56.sockpor/tmp/php-cgi-83-rnt.sock
no vhost — são 9 ocorrências, incluindo os blocos location específicos
de /admin/, /produtor/login.php e /produtor/action.php.
session.cookie_path="/; SameSite=Lax"no.user.inié hack para PHP < 7.3;
no 8.3 o certo é session.cookie_samesite=Lax com session.cookie_path=/.
- Rollback é uma linha: voltar o
fastcgi_passenginx -s reload.
Baseline
A tag php56-baseline não existia — o BK dizia que ela era o método de
referência, mas ninguém a criou. Criada em 25/08/2026 apontando para o commit
imediatamente anterior a esta fase. git diff php56-baseline -- <arquivo>
entrega a conferência arquivo por arquivo.
Script da virada — /root/v83.sh (25/08/2026)
Web e cron no mesmo ciclo, porque /usr/bin/php é symlink para o 5.6 e
virar só a web deixaria as automações em 5.6 lendo o mesmo config/connect.php
que a web lê em 8.3.
```
/root/v83.sh dry-run (padrao, nao altera nada)
/root/v83.sh a aplica
/root/v83.sh u desfaz
/root/v83.sh s status
```
Portão de pré-requisitos (o a recusa se algum falhar): pool 8.3 de pé,
php-fpm -t e nginx -t válidos, working tree do webroot limpo, nenhuma sonda
_bk348_probe.php no webroot, e cmp56x83.sh sem divergência nas 105 páginas.
Rollback automático: depois de trocar o fastcgi_pass ele faz smoke test em
12 páginas (incluindo /webhook_mp_ingressos.php e /pedido.php); se alguma
sair do esperado, restaura vhost e pool e sai com erro.
Escopo deliberadamente estreito — o script NÃO toca:
- o symlink
/usr/bin/php(compartilhado com edmundo10, sigaautobot, amoreterno); - o pool
[www]do 8.3 (divulgai e edmundo10); - o
php.inicompartilhado; - qualquer linha de cron de outro projeto — a troca é linha a linha, filtrando
por rionoteatro.com.br, e a contagem de linhas do crontab é conferida antes
de aplicar (se mudar, aborta).
Medido antes de escrever o script:
- o CLI do 8.3 não tem scan dir (
Scan this dir => (none)), então o
PHP_INI_SCAN_DIR=/www/rnt-reach/ini usado pelos /etc/cron.d/rnt-* continua
seguro: mysqli, curl, json, mbstring, gd e openssl seguem
carregando. Se o 8.3 tivesse scan dir, essa variável desligaria as extensões e
quebraria todo o cron.
admin/cron/analisa_push_ab.php --dry-run(o único cron que o próprio arquivo
documenta como seguro rodar à mão) produz saída byte a byte idêntica em
5.6 e 8.3, 32 linhas.
enable-php-83-rnt.conf é gerado pelo próprio script: é o enable-php-56.conf
com o socket trocado, para manter a estrutura que o aaPanel espera em vez de
inlinear o bloco no vhost.
VIRADA EXECUTADA (25/08/2026 12:36) — e o que ela ensinou
Produção passou a ser servida pelo PHP 8.3 às 12:36:28, web e cron no mesmo
ciclo, via /root/v83.sh a. Backup em
/root/backups-versionados/rnt-php83-virada-20260825-123628/.
CORREÇÃO DESTE DOCUMENTO: a arquitetura do pool é outra
A seção "FASE 2 EXECUTADA" acima diz que o pool [rnt] vem de
/www/server/php/83/etc/php-fpm.d/rnt.conf, incluído pelo php-fpm.conf.
Está errado, e ficou errado desde que foi escrito. O que existe de fato:
| | |
|---|---|
| config do master | /www/server/php/83/etc/php-fpm-rnt.conf — master separado, pid e log próprios |
| php.ini | /www/server/php/83/etc/php-rnt.ini |
| serviço | systemctl {status,restart} php-fpm-83-rnt (enabled, Restart=on-failure) |
php-fpm.d/rnt.conf não é carregado por ninguém — o php-fpm.conf do 8.3
não tem linha include. O arquivo continua em disco e deve ser removido; editar
ele não surte efeito nenhum (medido: memory_limit e error_reporting postos
ali foram simplesmente ignorados, e o diagnóstico só apareceu ao comparar
php-fpm -tt com os masters em ps).
**Para mudar disable_functions, error_reporting ou display_errors do RNT,
o arquivo é php-rnt.ini.** Duas correções aplicadas ali logo após a virada:
display_errors estava On (instrumentação de espelho vazando para produção —
só não apareceu porque o .user.ini do webroot força Off) e
error_reporting ganhou & ~E_DEPRECATED, que derrubou o log de uma requisição
de api/eventos/gerar_json.php de 2.656 linhas para 0.
A varredura pré-virada tinha um buraco: URLs amigáveis
As 105 URLs comparadas vinham do access log filtrando \.php — ou seja,
nenhuma URL amigável entrou. E URL amigável (/<slug> → peca.php?slug=) é
a superfície pública principal do site. Resultado: **um visitante real pegou 500
quatro vezes**, entre 12:46:45 e 12:47:40, na peça /lisa-lesa-e-louca.
Corrigido às 12:51.
Dois fatais, os dois invisíveis para o teste feito:
peca.php:1146fazia$horarios = "";e a iteração seguinte do laço
fazia $horarios[] = .... No PHP 5, atribuir [] a uma string vazia
convertia para array em silêncio; o PHP 7.1 removeu isso e o 8.3 lança
[] operator not supported for strings. Quebrava **toda peça com mais de um
dia de sessão** — /peca.php sem slug passava no teste, a página real não.
meta-analytics.phpé incluído de dentro deLoadHeader()
(includes/inc_peca.php:46), então $conexao, que é global, chega null.
No 5.6 mysqli_query(null, ...) era warning e devolvia false, e todo uso
ali já testa o retorno. No 8.3 é TypeError fatal.
A lição, para a próxima migração: filtrar o access log por \.php parece
razoável e não é — ele descarta justamente as rotas reescritas, que costumam ser
as mais acessadas. cmp56x83.sh foi corrigido para incluir URL amigável.
Varredura pós-correção: **400 páginas de peça por slug, 100 de /teatro/ e
/evento/, e as 105 .php** — zero erro, zero fatal.
Fallout pos-virada (registro continuo)
- 30/08/2026 —
BASE_URL(BK-362).admin/modulos/pedidos_2/index.php:465
usava uma constante que nunca existiu no projeto; no 5.6 virava string literal
(AJAX 404 calado), no 8.3 e fatal e cortou a saida da pagina na metade,
matando todo o JS abaixo — inclusive o envio de WhatsApp pelo painel. Passou
batido na varredura pre-virada porque o modulo nao foi acessado nenhuma vez
entre 26 e 29/08: **pagina sem acesso na janela de teste nao e pagina
validada**. Detalhe em docs/CHANGELOG.md.
- 30/08/2026 —
$_SERVER[HTTP_HOST]sem aspas (BK-362). Achado pela
varredura do item acima, em painel/modulos/clubernt/action.php:68, modulo
vivo (linkado de peca.php/checkout_pix.php, 1-5 acessos/dia). Mesma classe,
mesmo modo de falha silencioso no 5.6.
- Ferramenta:
/root/rnt-tools/const_undef.phpvarre constante usada e nunca
definida na arvore inteira (tem --selftest). **Rodar com
/www/server/php/83/bin/php**: sob o CLI 5.6 ele acusa IMAGETYPE_WEBP e
outros falsos positivos, porque a lista de constantes internas muda por versao.
A classe ja tinha sido varrida na virada (PATH_FILES, 38 bootstraps) e mesmo
assim dois casos passaram — varredura pontual por nome nao substitui varredura
por padrao.
Pendências
- remover
/www/server/php/83/etc/php-fpm.d/rnt.conf(inerte, engana quem ler); cmp56x83.shcompara127.0.0.1com:8383— os dois em 8.3 agora, então
ele deixou de comparar versões e virou um "produção x espelho". Para comparar
com o 5.6 seria preciso levantar um vhost apontando para php-cgi-56.sock;
utf8_encode()(1.327 chamadas emapi/eventos/gerar_json.php) está apenas
silenciado no log, não corrigido. Sai de vez no PHP 9.