Skip to content

ci: pin symfony/console to ^7 — Infection mutation gate broken by Symfony Console 8#29

Merged
Goosterhof merged 1 commit into
mainfrom
ci/pin-symfony-console-7
Jun 1, 2026
Merged

ci: pin symfony/console to ^7 — Infection mutation gate broken by Symfony Console 8#29
Goosterhof merged 1 commit into
mainfrom
ci/pin-symfony-console-7

Conversation

@Goosterhof
Copy link
Copy Markdown
Contributor

What

Pins symfony/console to ^7.2 in require-dev.

Why — repo-wide CI breakage, no source change

symfony/console v8.1.0 (released 2026-05-29) crashes Infection 0.33.x's mutation runner:

In Container.php line 194:
  Unknown service "Symfony\Component\Console\Helper\QuestionHelper"

Infection's DI container references QuestionHelper as a service Symfony Console 8 no longer registers that way, so composer mutation:ci aborts and exits 1 — failing the check job.

The package's composer.lock is gitignored, so CI resolves dependencies fresh on every run. illuminate/* v13 permits Symfony 8, so the resolver started pulling v8.1.0. Result: every fresh CI run went red on the mutation step fleet-wide — PRs green on 2026-05-28 turned red on 2026-05-29 with zero source change (this branch, main, and the three open rule PRs #26/#27/#28 all affected).

Fix + verification

Pinning symfony/console: ^7.2 resolves the dev toolchain down to v7.4.13 (illuminate v13 accepts it). Verified locally:

  • composer mutation:ci → 419 mutants, 340 killed, Covered Code MSI 81% ≥ 75, exit 0.
  • composer phpstan / composer test (90) / composer format:check / composer coverage:check (87.56%) all green.

Lifespan

Temporary. Remove or widen the constraint once Infection ships Symfony Console 8 support.

🤖 Generated with Claude Code

symfony/console v8.1.0 (2026-05-29) crashes Infection 0.33.x's mutation
runner with 'Unknown service Symfony\Component\Console\Helper\QuestionHelper',
failing composer mutation:ci on every fresh resolve. The lockfile is
gitignored so CI always resolves latest; illuminate v13 permits Symfony 8.

Pin holds the dev toolchain at symfony/console v7.4.x. Verified mutation
gate green (Covered Code MSI 81% >= 75). Revisit when Infection supports
Symfony Console 8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Goosterhof Goosterhof added the Agent Review Requested Requesting review of specialized AI review agents. label May 29, 2026
Copy link
Copy Markdown
Contributor Author

@Goosterhof Goosterhof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers, no concerns, one nit. This is a correctly-diagnosed, minimal dependency pin and the diagnosis checks out against the repo.

Verified the claims directly against origin/main:

  • composer.lock is gitignored (/.gitignore carries /composer.lock), so CI does resolve fresh on every run — the "green yesterday, red today with no source change" failure mode is real, not hypothetical.
  • The require block permits illuminate/* ^13, and Laravel 13 permits Symfony 8, so the fresh-resolve path to symfony/console v8.1.0 is the documented one.
  • symfony/console is the only Symfony component pinned, which is the right call: the break is specifically Infection's DI container resolving Symfony\Component\Console\Helper\QuestionHelper as a service, and that's a symfony/console 8 change. Pinning the whole Symfony surface would be over-broad blast radius for a single-component break.

Praise

The CHANGELOG entry is load-bearing, not ceremonial — it captures the gitignored-lock mechanism, the illuminate v13 → Symfony 8 resolution path, the exact Unknown service failure signature, and an explicit removal trigger (Infection ships Symfony Console 8 support). Anyone hitting a confusing transitive-dep CI failure on a sibling Infection-gated repo can grep this and self-serve. That's the right place for incident provenance.

Nit

The pin's only removal reminder lives in the CHANGELOG. A pin keyed to an upstream fix tends to outlive its cause silently — the gate goes green and nobody revisits. Consider a short inline tracking marker (a one-line // symfony/console ^7: Infection 0.33 QuestionHelper break — widen when Infection supports Symfony Console 8 comment isn't possible in JSON, so a tracked issue or a deferred.md Resolve By is the practical equivalent). Not blocking — the CHANGELOG is honest about the temporary nature.

Verdict: approve-worthy. The fix is one line, the diagnosis is verified, and the constraint widening (^7.2 rather than =7.4.13) leaves room for 7.x patch uptake. Merge once CI confirms the mutation gate is green on this head.

Copy link
Copy Markdown
Contributor

@jasperboerhof jasperboerhof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — reviewed, no blockers.

@Goosterhof Goosterhof merged commit 2fab389 into main Jun 1, 2026
2 checks passed
@Goosterhof Goosterhof deleted the ci/pin-symfony-console-7 branch June 1, 2026 10:37
Goosterhof added a commit that referenced this pull request Jun 1, 2026
New PHPStan rule banning Eloquent persistence-API method calls
(save/update/delete/create/destroy/forceDelete/forceFill/push/restore/touch
and their *OrFail / *Quietly / *OrCreate variants — 24-method blocklist) on
`Illuminate\Database\Eloquent\Model` subclasses and
`Illuminate\Database\Eloquent\Builder` chains when the call site is inside an
`App\Http\Controllers\*` class (including sub-namespaces via `str_starts_with`).
Reads (`find`, `where`, `get`, `first`, `paginate`, `pluck`, `count`, `exists`,
`query`) deliberately permitted — controllers reading Models is necessary for
route-model binding, ResourceData hydration, and policy checks.

Identifier: `forbidEloquentMutationInControllers.eloquentMutationInController`.
Doctrine source: ADR-0011 (Action Class Architecture) + ADR-0019 (Explicit
Model Hydration).

Algorithm:
- Namespace gate (`App\Http\Controllers` prefix)
- Recursively walk every `ClassMethod` body collecting `MethodCall`/`StaticCall`
- For `MethodCall`: type-aware `ObjectType::isSuperTypeOf()` against `Model` OR
  `Builder` matches the receiver; method name in blocklist fires
- For `StaticCall`: `Scope::resolveName()` resolves to Model subclass FQCN;
  method name in blocklist fires

Builder coverage uses resolution (a) per order §A7 — type-aware
`ObjectType::isSuperTypeOf()` handles `Builder<User>` as a subtype of the
unparameterized `Builder` cleanly, no brittle generic introspection needed.
Verified empirically by `ViolationBuilderUpdate.php` fixture firing at the
`->update([...])` line of a `User::query()->where(...)->update([...])` chain.

Supersedes consumer-side string-match Pest arch tests in kendo, ublgenie,
entreezuil, and the ISMS bridge subset from PR #10. Cross-territory cascade is
the General's follow-up dispatch after this lands; emmie + BIO pick up coverage
automatically on next composer update.

14 tests, 14 assertions; PHPStan max self-analysis clean (10 services); Pint
clean; line coverage 87.98% (new rule 91.01%) ≥83 gate; MSI 82%, Covered Code
MSI 82% (both ≥75 gate). The 7 escaped mutants on the new rule are all in the
recognizable Standing Concern #29 walkNodes() parity family + MBString-
equivalent + defensive `??` defaults — same shape as the precedent on the
other four rules.

Closes war-room enforcement queue #87.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Agent Review Requested Requesting review of specialized AI review agents.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants