conduction/hydra-gates
Hydra's mechanical quality gates, packaged so any repo can run them against its own diff. The exit code is the failure COUNT.
v1.9.0
60K downloads/mo
#1859 most downloaded on Packagist
ConductionNL/.github
What this package is like to depend on
Last release 2 days ago
22 Aug 2026
Ships on a steady schedule
a new release about every 8 days
Some releases are documented
notes for 10 of 18 stable releases
Nothing withdrawn
no release was ever pulled
0 months old
18 releases · first in 2026
18 releases in the last 12 months
see the full history below
Release timeline
18 releases · Aug 2026 to Aug 2026Releases
latest 18-
v1.9.022 Aug 2026Release notes
Open source →The package no longer declares
"autoload": { "psr-4": { "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" } }That prefix was LONGER than openregister's own OCA\OpenRegister\ -> lib/, PSR-4
is longest-prefix-wins, and the package installs into every consumer's vendor/ —
so whichever app's autoloader registered first defined OpenRegister's contract
for the whole process. Measured on a real instance: softwarecatalog's vendored
copy was supplying openregister's own interface (.github#514, #531).The contracts still ship. A consumer that needs them requires them from its own
test bootstrap behind interface_exists(), which is order-independent. See
hydra-gates/README.md, 'The shipped OpenRegister contracts are opt-in'.MINOR, not MAJOR, deliberately: every app constrains this package with a ^1
caret, so a 2.0.0 would reach nobody and the fleet would keep the defect. The
four consumers that relied on the implicit autoload — buildiq, decidiq, filinq,
stackiq — are migrated and verified on development ahead of this tag. The other
apps either ship their own contract stubs (dossiq, pipelinq, shillinq), own the
real one (openregister), or never reference it. -
v1.8.220 Aug 2026Release notes
Open source →quality-config/phpstan-base.neon gains two settings so the fleet's nine
phpstan/phpstan 1.12.33 -> 2.x dependabot PRs stop failing on migration
behaviour rather than on defects:treatPhpDocTypesAsCertain: false 2.x narrows types declared only in a
docblock and reports the defensive
checks against them as already-decided.
ignoreErrors phpDoc.parseError @SuppressWarnings is PHPMD's tag; 2.x
parses it as its own and fails on
PHPMD's rule syntax.Measured on decidesk with 2.2.8: 60 -> 22 errors. The residual 22 are real
per-app debt and are NOT masked.Verified on BOTH majors, since 17 of 18 apps still run 1.12.33:
2.2.8 60 -> 22 errors
1.12.33 exit 0, '[OK] No errors', no configuration error, and still
'Found 1 error' on a deliberately broken file — so it is
analysing, not silently skipping. -
v1.8.120 Aug 2026Release notes
Open source →Cut because v1.8.0 shipped an ObjectServiceInterface WITHOUT patchObject()
and with updateObject() still summarised as "Apply a partial update to an
existing object" — the exact wording that sent a consumer down the erasing
path. The correction landed on main in f8cad2f two days after v1.8.0 was
tagged, and every app has been pinned to the broken copy ever since.That is not confined to documentation. hydra-gates claims the app's own
namespace in its composer autoload:"psr-4": { "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" }which is a LONGER prefix than openregister's own OCA\OpenRegister\ -> lib/,
so the gate package wins. Nine repos vendor it, so under OC_App::loadApps()
whichever app registers first defines the contract for the whole instance.
Measured on a running instance: softwarecatalog's vendor directory was
supplying openregister's interface, and patchObject() — the read-merge-write
path — was unreachable through the type consumers bind to.openregister/tests/Unit/Service/ObjectServiceUpdateVersusPatchTest catches
all three symptoms; it has been failing on that, and only that, out of
16 839 tests.Also carries everything else merged on main since v1.8.0, including gate
behaviour changes (phpcs errors fail the gate #483, gate-8 judges how the
caller consumes the null #506, coverage-guard ignores deletions #480) and
the release step now bumping openapi.json alongside appinfo/info.xml (#515,
which had left all six of opencatalogi's PHPUnit cells red). -
v1.8.015 Aug 2026 -
v1.7.312 Aug 2026Release notes
Open source →v1.7.2 appended the
(?)optional marker to two excludePaths entries. Measured
against the real apps, that made PHPStan 2.x WORSE: NEON reads… (?)after a
%…%expansion as an ENTITY, so 2.x died withFileExcluder::isAbsolutePath(): Argument #1 ($path) must be of type string,
Nette\DI\Definitions\Statement givenQuoting the whole value fixes 2.x but breaks 1.x, which then does NOT strip the
marker and resolves the literal "/app/vendor-bin (?)" — a path matching nothing.
Harmless where the directory is absent, and silently breaking where it exists:
openbuild ships 31 files under lib/Resources/template.So a conditional path has no spelling that is safe on both majors, and it leaves
the base.vendoris the only entry that always exists. The other two move to
the apps that actually have the directory:vendor-bin openregister only
lib/Resources/template portaliq, openbuild, hermiqVerified on zaakafhandelapp (PHPStan 2.2.8, the fleet's only 2.x app) with its
temporary override removed: 57 files analysed, no errors. The[OK] No errors
line was not taken as proof — a zero-file run prints the same thing, which was
the original defect. -
v1.7.212 Aug 2026Release notes
Open source →excludePaths named
vendor-binandlib/Resources/templatewithout the(?)
optional marker. PHPStan 2.x VALIDATES excludePaths and aborts when an entry
names something that does not exist; 1.x accepts it silently, which is why this
went unnoticed through v1.7.0 and v1.7.1.zaakafhandelapp is the only fleet app on PHPStan 2 (2.2.8; the other seventeen
are on 1.12.33). It has neither directory, so including the base unchanged made
composer phpstanexit before analysing a single file — the same failure shape
as v1.7.0's relative paths, from a different cause.vendorstays mandatory: phpstan runs from vendor/bin, so it always exists.The marker is stripped during resolution, so no app's resolved value changes —
verified on docudesk (PHPStan 1.12.33) with a plantedno-such-dir-control (?),
which analysed cleanly and dumped as a plain path. -
v1.7.112 Aug 2026Release notes
Open source →The base shipped in v1.7.0 declaring paths, bootstrapFiles, excludePaths and
scanDirectories as bare relative paths. PHPStan resolves those against the
config file that DECLARES them, so consumed from
vendor/conduction/hydra-gates/quality-config/ they pointed at
vendor/.../quality-config/lib — which does not exist. The run aborts with
'Path ... does not exist', or worse analyses nothing.It was invisible for the same reason every dead gate in this fleet's history has
been: no app had adopted it yet, so nothing was red. The defect shipped inside a
release whose whole purpose was to centralise configuration.This is also the SECOND time today the same mechanic has bitten. quality-config/
phpcs.xml carries a load-bearing comment explaining that phpcs resolves
relative to the declaring ruleset and that the app stub must therefore own it —
and phpstan-base.neon was written straight afterwards with the identical mistake
in a different tool's syntax. Knowing the general rule did not transfer.Fixed with %currentWorkingDirectory% prefixes. Verified on larpingapp with
phpstan dump-parameters: level, paths, excludePaths, bootstrapFiles and
scanDirectories all resolve to byte-identical values against a local config,
findings unchanged. -
v1.7.012 Aug 2026 -
v1.6.007 Aug 2026 -
v1.5.206 Aug 2026Release notes
Open source →v1.5.1 counted gate-28 as APPLICABLE whenever
lib/existed and
composer.json declared a license, regardless of whether the PR touched a
single PHP file. A PR that changes only a workflow, a lockfile, a spec or an
info.xml therefore fails on:[gate-28] license-triangle: SKIPPED (structural) — ... 0 in-scope
lib/**/*.php file carried an @license ... so NOTHING was comparedwith
hydra-gates-require-full-coverageon, which is now the default. The
gate was right that it compared nothing; it was wrong about why. Nothing was
in scope, which is ADR-020's diff-scoping doing its job, and that is exactly
what the NOT-APPLICABLE classification exists to say.Tagged because the fix has been sitting on main untagged, and every consumer
pins a tag — an untagged fix reaches nobody. Observed on openregister#2365,
whose diff is info.xml + package.json + a lockfile + one spec. -
v1.5.106 Aug 2026Nothing published for this version
-
v1.5.006 Aug 2026Nothing published for this version
-
v1.4.005 Aug 2026Nothing published for this version
-
v1.3.005 Aug 2026Nothing published for this version
-
v1.2.005 Aug 2026Nothing published for this version
-
v1.1.004 Aug 2026Nothing published for this version
-
v1.0.103 Aug 2026Nothing published for this version
-
v1.0.003 Aug 2026Nothing published for this version