diff --git a/composer.json b/composer.json index 6cb96acd..3d185aea 100644 --- a/composer.json +++ b/composer.json @@ -18,11 +18,6 @@ "bin": [ "hydra-gates/bin/hydra-gates" ], - "autoload": { - "psr-4": { - "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" - } - }, "require": { "php": ">=8.1" }, diff --git a/docs/hydra/operations/app-health.md b/docs/hydra/operations/app-health.md index 850cffd3..6975abca 100644 --- a/docs/hydra/operations/app-health.md +++ b/docs/hydra/operations/app-health.md @@ -287,10 +287,19 @@ day. Neither number was a decision; both were the absence of one. The cost was not theoretical. hydra-gates v1.8.0 shipped an `ObjectServiceInterface` **without `patchObject()`**, and because the package -claims `OCA\OpenRegister\Contract\` in its composer autoload — a *longer* -psr-4 prefix than openregister's own — a stale copy in **any** app's vendor -directory defined that contract for the whole instance. Measured: -softwarecatalog's vendor was supplying openregister's interface. Separately, +*used to claim* `OCA\OpenRegister\Contract\` in its composer autoload — a +*longer* psr-4 prefix than openregister's own — a stale copy in **any** app's +vendor directory defined that contract for the whole instance. Measured: +softwarecatalog's vendor was supplying openregister's interface. + +> ✅ **That mechanism is gone.** The prefix was removed from the package +> (ConductionNL/.github#531); the contracts still ship, but a consumer now opts +> in from its own test bootstrap behind `interface_exists()`, which is +> order-independent. Four apps needed the opt-in — buildiq, decidiq, filinq and +> stackiq. The others either ship their own contract stubs (dossiq, pipelinq, +> shillinq), own the real one (openregister), or never reference it. + +Separately, nc-vue's AI-companion singleton landed in 2.7.0, so every app below it rendered a **second** companion hex beside hermiq's on every page. Both fixes had existed upstream for weeks. diff --git a/hydra-gates/README.md b/hydra-gates/README.md index 5f346332..b2aa053b 100644 --- a/hydra-gates/README.md +++ b/hydra-gates/README.md @@ -146,6 +146,52 @@ gap, not a pass — see *Reading a green*. --- +## The shipped OpenRegister contracts are opt-in, not autoloaded + +`hydra-gates/contracts/` ships `ObjectServiceInterface` and +`ObjectEntityInterface` so a leaf app can typehint OpenRegister's data-access +surface under PHPUnit without the OpenRegister app installed. + +They are **not** autoloaded. This package used to declare + +```json +"autoload": { "psr-4": { "OCA\\OpenRegister\\Contract\\": "hydra-gates/contracts/" } } +``` + +and that was a bug, not a convenience. The prefix is *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, and a v1.8.0 copy without +`patchObject()` broke callers of the real one (ConductionNL/.github#531). + +A consumer that needs these interfaces requires them from its own test +bootstrap, behind a guard: + +```php +foreach (['ObjectEntityInterface', 'ObjectServiceInterface'] as $contract) { + if (interface_exists('\\OCA\\OpenRegister\\Contract\\' . $contract) === false) { + $shipped = __DIR__ . '/../vendor/conduction/hydra-gates/hydra-gates/contracts/' . $contract . '.php'; + if (file_exists($shipped) === true) { + require_once $shipped; + } + } +} +``` + +Put it **before** anything that implements the interface — a stub entity that +`implements \OCA\OpenRegister\Contract\ObjectEntityInterface` fatals inside the +bootstrap otherwise, which is a dead run rather than a failed test. And put it +in the bootstrap `phpunit.xml` actually loads: several apps ship two or three. + +`interface_exists()` rather than a fallback autoloader, because +`spl_autoload_register` appends relative to *registration order*, and +registration order across independently loaded apps is exactly the thing nobody +controls. Asking whether the interface is resolvable is order-independent. + +--- + ## What it needs at runtime `bash`, `git`, `python3` (about twenty gates are Python helpers) and `node`