Skip to content

fix(schema): a fresh install of ffc_reregistration_submissions lacked auth_code and magic_token (#1087 passo 5) - #1093

Merged
rpgmem merged 2 commits into
developfrom
claude/reregistration-create-table-4vvuer
Sep 7, 2026
Merged

fix(schema): a fresh install of ffc_reregistration_submissions lacked auth_code and magic_token (#1087 passo 5)#1093
rpgmem merged 2 commits into
developfrom
claude/reregistration-create-table-4vvuer

Conversation

@rpgmem

@rpgmem rpgmem commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Passo 5, o último da #1087. Mesma classe da #1091, na outra tabela que declara schema em dois lugares — e muito menor do que eu havia desenhado, porque a medição derrubou metade da premissa antes de eu escrever qualquer linha.

O que é real

O CREATE TABLE de ffc_reregistration_submissions tinha 11 colunas. auth_code e magic_token não estavam entre elas: chegavam só pelo add_columns_if_missing(), na primeira mudança de FFC_VERSION. Agora são 13 colunas e 6 chaves, com os dois índices que aquele caminho também cria.

A retificação, que veio antes do código

Ao desenhar este passo eu afirmei — na #1087, no corpo da #1091 e duas vezes ao mantenedor — que três tabelas rodavam ORDER BY created_at sem índice numa instalação nova, porque maybe_add_perf_indexes() só roda quando FFC_VERSION muda.

É falso. As três já declaram KEY idx_created no próprio CREATE TABLE:

Tabela Onde
ffc_recruitment_notice class-ffc-recruitment-activator.php:581
ffc_recruitment_candidate class-ffc-recruitment-activator.php:669
ffc_reregistration_submissions no CREATE de 11 colunas do seu activator

maybe_add_perf_indexes() é rede defensiva para instalações antigas. Eu deduzi da existência do add_index_if_missing() sem abrir os CREATE TABLE, e generalizei a partir do caso do ffc_submissions, onde os dois índices compostos de fato faltavam. Retificado na #1087 e na #1091 antes de começar.

Então o passo 5 encolheu de "2 colunas + 3 índices, com guarda generalizado" para 2 colunas em uma tabela.

O guarda: varredura, não framework — e isso é deliberado

O plano previa "generalizar o guarda para a classe inteira". Com a população real sendo dois add_columns_if_missing() no repositório inteiro, um motor declarativo de schema seria a indireção que não estreita nada — a armadilha que o CLAUDE.md nomeia, e a mesma recusa que fiz no #1079 contra um Core\OptionValue::int() para três sites.

O que o SchemaAgreementTest faz (substituindo o SubmissionsSchemaAgreementTest, que ele subsume) é procurar os add_columns_if_missing() que existem e conferir cada um contra o CREATE TABLE do mesmo arquivo. Um terceiro caso fica coberto no dia em que for escrito, sem abstração nova.

Verificado nos dois casos, cada um nomeando arquivo e coluna:

A column that add_columns_if_missing() gives an existing install is absent from every
CREATE TABLE in the same file, so a fresh install would not have it:
  class-ffc-reregistration-activator.php :: auth_code
  class-ffc-activator.php :: ticket_hash

Um terceiro teste garante que a varredura não devolve vazio — sem ele, no dia em que o padrão quebrar, as outras asserções passariam vazias. É o defeito do #1071 um nível acima, e o mesmo cuidado que o guarda anterior já tomava.

Ele compara nomes, não tipos, pelo mesmo motivo da #1091: as duas fontes escrevem tipos diferente por construção, e normalizar isso seria reimplementar a comparação do dbDelta dentro de um teste.

A guarda table_exists() fica

Pelo mesmo motivo da #1091: removê-la para deixar o dbDelta cuidar do upgrade é o cenário que o CLAUDE.md nomeia como gatilho dos defeitos latentes, e mudaria o comportamento de toda instalação a cada bump. Consolidar as colunas não precisa disso.

Validação

  • ActivatorSqlTest: verde — as duas regras de dbDelta valem para o CREATE alterado
  • SchemaAgreementTest: 3 testes, a asserção principal verificada falhando contra remoção proposital em ambas as tabelas
  • PHPStan nível 8, WPCS e phpcs-tests.xml.dist: limpos
  • Suíte completa: 7657 testes, 21955 asserções, verde (11m40s)
  • Não verificado aqui: não há MySQL no ambiente, então o dbDelta não rodou. Quem exercita o caminho alterado é o gate fresh-install do CI, contra MariaDB 11.8 num banco vazio

Refs #1087, #1091, #1079, #994

🤖 Generated with Claude Code

https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU


Generated by Claude Code

… auth_code and magic_token (#1087 passo 5)

Mesma classe da #1091, na outra tabela que declara schema em dois lugares. O
CREATE TABLE tinha onze colunas; auth_code e magic_token so chegavam pelo
add_columns_if_missing(), na primeira mudanca de FFC_VERSION. Agora sao treze
colunas e seis chaves, com os dois indices que aquele caminho tambem cria.

A guarda 'if ( table_exists() ) return;' fica, pelo mesmo motivo da #1091:
remove-la para deixar o dbDelta cuidar do upgrade e o cenario que o CLAUDE.md
nomeia como gatilho dos defeitos latentes, e mudaria o comportamento de toda
instalacao a cada bump.

O guarda de acordo passa a varrer os activators em vez de mirar uma tabela:
SubmissionsSchemaAgreementTest vira SchemaAgreementTest, que procura os
add_columns_if_missing() existentes e confere cada um contra o CREATE TABLE do
mesmo arquivo. Verificado nos dois casos — remover auth_code do reregistration e
ticket_hash do submissions falham, cada uma nomeando arquivo e coluna.

NAO construi framework, e isso e deliberado: a populacao real e de dois
add_columns_if_missing() no repositorio inteiro. Um motor declarativo de schema
sobre dois sites seria a indirecao que nao estreita nada — a armadilha que o
CLAUDE.md nomeia, e a mesma recusa que fiz no #1079 contra um OptionValue::int
para tres sites. Um terceiro caso fica coberto no dia em que for escrito.

RETIFICACAO. Ao desenhar este passo eu afirmei, na #1087 e no corpo da #1091,
que tres tabelas rodavam ORDER BY created_at sem indice numa instalacao nova.
E falso: ffc_recruitment_notice, ffc_recruitment_candidate e
ffc_reregistration_submissions ja declaram KEY idx_created no proprio CREATE
TABLE. O maybe_add_perf_indexes() e rede defensiva para instalacoes antigas.
Eu deduzi da existencia do add_index_if_missing() sem abrir os CREATE TABLE, e
generalizei a partir do caso do ffc_submissions, onde os dois indices compostos
de fato faltavam. Retificado nos dois lugares.

Refs #1087, #1091, #1079

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
@rpgmem
rpgmem enabled auto-merge (squash) September 7, 2026 01:08
@rpgmem
rpgmem merged commit 27a1a44 into develop Sep 7, 2026
20 checks passed
@rpgmem
rpgmem deleted the claude/reregistration-create-table-4vvuer branch September 7, 2026 01:22
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34072045854

Warning

No base build found for commit 7cddac1 on develop.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 89.971%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 53944
Covered Lines: 48534
Line Coverage: 89.97%
Coverage Strength: 4.87 hits per line

💛 - Coveralls

rpgmem added a commit that referenced this pull request Sep 7, 2026
… nothing measured it (#1087 passo 7) (#1095)

* test(schema): dbDelta re-ALTERs forever when a CREATE TABLE lies, and nothing measured it (#1087 passo 7)

`dbDelta()` ALTERs away the difference between a `CREATE TABLE` and what
MySQL actually stored. When the two disagree in a way the statement can
never satisfy, it re-ALTERs on every single run, forever, in silence.

No gate saw that class. `ActivatorSqlTest` reads the statement as text,
with no database. The `fresh-install` job starts from an empty schema, so
it only ever watches the CREATE succeed — never the second pass. The
post-deploy smoke asserts the tables exist, not that the schema still
matches. #997 measured it exactly once, by hand, against a real MariaDB
(41 database errors on the self-scheduling tables, 1 after the cleanup)
and never again — while two CREATE statements changed in #1091 and #1093.

The new step runs in `fresh-install`, the only job with a real MariaDB
AND a freshly created schema, and replays every CREATE through
`dbDelta( $sql, false )` against the table it just built. The change list
must be empty; anything in it is an ALTER on every future activation.

The extraction moves to `.github/scripts/ffc-create-statements.php` so
this gate and `ActivatorSqlTest` measure the same 37 statements — the
rule `uninstall.php` already follows as one manifest, and the direct
lesson of steps 3 and 6: two private scans of one thing is how a
denominator goes wrong unnoticed (the row ruler saw 45 of 53 classes).

The gate fails on blindness rather than passing: an empty scan, a count
that diverges from a wider net (single quotes, heredoc), or a table name
it cannot resolve all abort instead of counting as clean.

Recorded along the way: `ffc_device_signals` is the only table whose
dbDelta is deliberately not guarded by `table_exists()`, so it already
runs against an existing table on every activation in every install —
the #997 class, live and never measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU

* fix(ci): `wp eval-file` eval()s the script, where declare(strict_types=1) is fatal

The gate failed on its first run for a reason that has nothing to do with
the schema: `wp eval-file` reads the file and `eval()`s it, and a
`declare(strict_types=1)` is only legal as the first statement of a
script — inside an eval it is a fatal error, so the step died before
reaching a single CREATE TABLE.

Load WordPress the way `fresh-install-check.php` does in this same job
instead: take the wp root as an argument and `require` its `wp-load.php`.
Same invocation shape as its sibling, the declaration stays, and the
command that eval()s files is out of the loop.

Verified: the original failure reproduces exactly (`eval()` of this file
before the change → "strict_types declaration must be the very first
statement in the script"), and after it the file parses whole and stops
at its own usage guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU

* test(schema): the first measurement since #997 finds 14 statements dbDelta re-ALTERs (#1087 passo 7)

The gate ran for real and did not come back empty. Against tables it had
just created from those very statements, `dbDelta()` wanted 33 changes
across 14 `CREATE TABLE` literals — an ALTER on every future activation,
in every install.

Three families, and only one is a schema question:

1. Display width the server supplies. The statement writes `int unsigned`
   and MariaDB stores `int(10) unsigned`; dbDelta compares text and
   ALTERs the difference forever. 9 statements.
2. `json`, which MariaDB implements as `longtext` plus a CHECK, so the
   statement asks for `json` and `SHOW CREATE TABLE` answers `longtext`.
   6 statements.
3. A column or index the live table does not have —
   `ffc_reregistrations.audience_id` and a `KEY auth_code` on two tables.
   Not cosmetic: statement and table genuinely disagree, and three files
   declare `ffc_custom_fields` while three declare
   `ffc_reregistration_submissions`.

So the gate blocks against a frozen baseline rather than against zero, in
the shape this repository already uses four times over (module boundary,
vacuous tests, superglobal casts, the row ruler): a change not listed
fails, and a listed change that stops happening also fails, so a fix is
locked in the moment it lands. Fixing the 14 is follow-up work on #1087,
not something to fold into the pull request that adds the measurement.

Two things the run settled. `ffc_device_signals` — the only table whose
dbDelta is not guarded by `table_exists()`, so it re-runs on every
activation in every install — is NOT among the 14. It was the obvious
suspect and it is clean; the drift is in tables where the repeated ALTER
is latent instead. And there is no local way to regenerate the baseline,
since it needs a live MariaDB, so the gate prints the exact block to
paste and dumps `SHOW CREATE TABLE` for the statements whose
disagreement is not a type spelling — the data family 3 needs.

`CreateStatementsParserTest` gains a check that every baseline key still
names a file that declares that table: an entry whose statement moved
would otherwise match nothing and fail nothing, which is the same silent
exemption this issue has spent three steps on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants