Summary
conduction/hydra-gates v1.8.0 — the latest tag, and the version every app has in composer.lock — ships an ObjectServiceInterface that is missing patchObject() and documents updateObject() with the wording that previously caused silent data loss. The fix is already on main but has never been released.
|
patchObject |
"Apply a partial update to an existing object." |
| v1.8.0 (installed everywhere) |
❌ absent |
⚠️ present |
| main |
✅ present |
✅ gone |
f8cad2f0 "fix(contracts): ship patchObject() so the published contract has one definition" (2026-08-17) landed after v1.8.0 was cut (9fc64ab9, 2026-08-15).
Why this is not cosmetic
hydra-gates/composer.json claims the app's own namespace at runtime:
"autoload": { "psr-4": { "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" } }
That prefix is longer than openregister's own OCA\OpenRegister\ → lib/, so PSR-4 longest-prefix-wins hands the class to the gate package — openregister's autoload_classmap.php points at it explicitly. Nine repos vendor it (decidesk, hermiq, larpingapp, nldesign, openconnector, openregister, portaliq, scholiq, softwarecatalog), so under OC_App::loadApps() the winner is whichever app registers first. Measured in a running instance:
WINNER: /var/www/html/custom_apps/softwarecatalog/vendor/conduction/hydra-gates/
hydra-gates/contracts/ObjectServiceInterface.php
hasPatchObject: NO
softwarecatalog's vendor directory defined openregister's contract. Patching openregister's own vendored copy changed nothing — another app still won.
Consequence: patchObject() — the read-merge-write path — is unreachable through the interface consumers type-hint, and the summary line they read in an IDE tooltip still recommends the erasing path.
Evidence it is caught, and is a true positive
openregister/tests/Unit/Service/ObjectServiceUpdateVersusPatchTest fails 1 error + 2 failures against v1.8.0:
testThePublishedContractDeclaresBothWritePaths
testTheContractDoesNotDescribeUpdateObjectAsAPartialUpdate
testThePublishedPatchSignatureMirrorsTheImplementation
Full local run, inside a booted Nextcloud: 16 839 tests, 37 875 assertions — these 3 are the only real failures. Please do not "fix the tests".
Ask
Cut v1.8.1 from main so the released package matches the source of truth, then let the apps pick it up.
⚠️ Sizing this deliberately, because it is not a one-file release: 56 commits are unreleased on main since v1.8.0, including gate-behaviour changes (phpcs errors must fail the gate; warnings still pass #483, gate-8: judge how the CALLER consumes the null #506, coverage-guard: deletions are not coverage regressions #480). Those will reach all 18 apps at once. The gate scripts already run from .github@main, so their behaviour is live regardless; the tag mainly propagates contracts/ and quality-config/. Still worth a deliberate release rather than an unattended one — which is why this is an issue and not a PR.
Found while running the fleet's suites locally during a GitHub Actions outage.
Summary
conduction/hydra-gatesv1.8.0 — the latest tag, and the version every app has incomposer.lock— ships anObjectServiceInterfacethat is missingpatchObject()and documentsupdateObject()with the wording that previously caused silent data loss. The fix is already onmainbut has never been released.patchObject"Apply a partial update to an existing object."f8cad2f0"fix(contracts): ship patchObject() so the published contract has one definition" (2026-08-17) landed after v1.8.0 was cut (9fc64ab9, 2026-08-15).Why this is not cosmetic
hydra-gates/composer.jsonclaims the app's own namespace at runtime:That prefix is longer than openregister's own
OCA\OpenRegister\→lib/, so PSR-4 longest-prefix-wins hands the class to the gate package — openregister'sautoload_classmap.phppoints at it explicitly. Nine repos vendor it (decidesk, hermiq, larpingapp, nldesign, openconnector, openregister, portaliq, scholiq, softwarecatalog), so underOC_App::loadApps()the winner is whichever app registers first. Measured in a running instance:softwarecatalog's vendor directory defined openregister's contract. Patching openregister's own vendored copy changed nothing — another app still won.
Consequence:
patchObject()— the read-merge-write path — is unreachable through the interface consumers type-hint, and the summary line they read in an IDE tooltip still recommends the erasing path.Evidence it is caught, and is a true positive
openregister/tests/Unit/Service/ObjectServiceUpdateVersusPatchTestfails 1 error + 2 failures against v1.8.0:testThePublishedContractDeclaresBothWritePathstestTheContractDoesNotDescribeUpdateObjectAsAPartialUpdatetestThePublishedPatchSignatureMirrorsTheImplementationFull local run, inside a booted Nextcloud: 16 839 tests, 37 875 assertions — these 3 are the only real failures. Please do not "fix the tests".
Ask
Cut v1.8.1 from
mainso the released package matches the source of truth, then let the apps pick it up.mainsince v1.8.0, including gate-behaviour changes (phpcs errors must fail the gate; warnings still pass#483,gate-8: judge how the CALLER consumes the null#506,coverage-guard: deletions are not coverage regressions#480). Those will reach all 18 apps at once. The gate scripts already run from.github@main, so their behaviour is live regardless; the tag mainly propagatescontracts/andquality-config/. Still worth a deliberate release rather than an unattended one — which is why this is an issue and not a PR.Found while running the fleet's suites locally during a GitHub Actions outage.