Cerebro Studio · Backlog · Changelog
RioNoTeatro • /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md
Abrir Studio Projeto externo em modo read-only; encaminhamento permitido, escrita bloqueada.

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)

Detalhe do BK Selecionado

/www/wwwroot/rionoteatro.com.br/docs/backlog/BK-348-migracao-php56-para-83.md • 2026-08-30T19:38:00.395Z

[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/$_GET direto, sem isset
  • 30 usos de each(), 33 de ereg/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.php já 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 .php de backup. action_backup_20251022_2.php executa

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/ e painel_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.ini do 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_file nao se aplica a php -r no 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.php 13→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

  1. 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.

  1. 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.

  1. *mysql_ removida.** config/mysql_compat.php, shim sobre mysqli, coberto

abaixo.

  1. Classe Object é reservada desde o 7.2 — derrubava painel/login.php e

painel/login_produtor.php. Renomeada para RntLegacyObject nos dois

obeject.php e nos dois security.php que estendem.

  1. 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.

  1. <?php sem 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.

  1. 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

  1. Remover do pool rnt as duas linhas marcadas TEMPORARIO

(display_errors = On e error_reporting = E_ALL).

  1. Virar o cron para o 8.3 no mesmo ciclo (senão split-brain).
  2. Trocar fastcgi_pass unix:/tmp/php-cgi-56.sock por /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.

  1. 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=/.

  1. Rollback é uma linha: voltar o fastcgi_pass e nginx -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.ini compartilhado;
  • 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:

  1. peca.php:1146 fazia $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.

  1. meta-analytics.php é incluído de dentro de LoadHeader()

(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.php varre 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.sh compara 127.0.0.1 com :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 em api/eventos/gerar_json.php) está apenas

silenciado no log, não corrigido. Sai de vez no PHP 9.