Skip to content

fix(apphost): the class_exists guard has been answering false — Bootstrap::register() has never run - #139

Closed
rubenvdlinde wants to merge 1 commit into
developmentfrom
fix/apphost-autoload-prelude
Closed

fix(apphost): the class_exists guard has been answering false — Bootstrap::register() has never run#139
rubenvdlinde wants to merge 1 commit into
developmentfrom
fix/apphost-autoload-prelude

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

This is the acute one

lib/AppInfo/Application.php guards its AppHost adoption:

if (class_exists(Bootstrap::class) === true) {
    Bootstrap::register($context, self::APP_ID, [...]);
} else {
    error_log('OpenBuild: OpenRegister AppHost\\Bootstrap is not autoloadable — ...');
}

That probe answers false on a perfectly healthy instance. OC_App::getEnabledApps() sort()s the app list and Coordinator::registerApps() calls registerAutoloading() then register() one app at a time — and openbuild sorts before openregister, so OCA\OpenRegister\ is not autoloadable at this point.

So Bootstrap::register() has apparently never run in this app. Everything it wires is silently absent:

  • the generic dashboard / settings / preferences controllers
  • the observability (health + metrics) controllers
  • the install repair steps
  • the admin settings panel + section
  • the manifest-driven deep-link listener (which replaced the deleted DeepLinkRegistrationListener)

The guard was not wrong to exist — it is what stopped the \Error from aborting the whole register() and taking the per-Application RBAC guards and the hybrid metadata-lock with it. It was just guarding against a condition the app itself creates by not registering the autoloader first. The else branch's error_log should be in every instance's log today.

The fix

Add the prelude before the probe. registerAutoloading() touches only the autoloader and is idempotent ($alreadyRegistered key guard); it does not boot the app. IAppManager::loadApp('openregister') would be wrong — it marks OpenRegister loaded and calls Coordinator::bootApp(), booting it before its own register() has run.

The class_exists guard is deliberately kept: it is still the correct behaviour when OpenRegister is genuinely absent or disabled.

Verified both directions, against this tree

gate-64
without the prelude rc=1 FAIL — AppHost adoption with no prelude
with the prelude rc=0 PASS

Checked under both matcher versions (.github main and the named-constant fix in .github#185).

⚠️ Reviewer, please note this is a real behaviour change, not just a gate fix. With the prelude in place class_exists answers true and Bootstrap::register() runs for the first time, registering the generics listed above. The domain overrides that follow are expected to win over the generic aliases — that expectation has never actually been exercised, so it is worth a look at the first CI run rather than assuming.

php -l clean.

🤖 Generated with Claude Code

ADR-040's load-order hazard, caught by gate-64 once the gate package was
actually reaching this repo.

OC_App::getEnabledApps() sort()s the app list, and Coordinator::registerApps()
walks THAT sorted list calling OC_App::registerAutoloading($appId, $path) and
then $app->register() for ONE APP AT A TIME. So every app's register() runs
BEFORE the PSR-4 prefix of every alphabetically-later app exists — on a
completely healthy instance, with OpenRegister enabled.

The fix is to put OpenRegister's prefix on the autoloader ourselves, which is
exactly what Nextcloud does a few iterations later. Two properties make this
the right call: registerAutoloading() touches ONLY the autoloader and is
idempotent (it early-returns on an $alreadyRegistered key), and it does NOT
boot the app. IAppManager::loadApp('openregister') would be wrong here — it
sets loadedApps['openregister']=true and calls Coordinator::bootApp(), booting
OpenRegister BEFORE its own register() has run.

Verified both directions with the gate itself, against this tree:

  without the prelude   rc=1  FAIL — AppHost adoption with no prelude
  with the prelude      rc=0  PASS

and under BOTH matcher versions (the one on .github main and the named-constant
fix in .github#185), so this is not a repo change chasing a gate change.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 41ef892

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
composer ✅ 101/101
npm ✅ 654/654
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-06 08:25 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Superseded by #138.

Both PRs add the ADR-040 prelude, but this one adds it inline in Application::register(). That fails the constraint the fix exists to satisfy: Application cannot be constructed without a Nextcloud DI container, so an inline prelude is unreachable from a unit test and its "never throws" contract cannot be asserted. #138 puts it in lib/AppInfo/OpenRegisterAutoloader.php with tests/Unit/AppInfo/OpenRegisterAutoloaderTest.php covering never-throws, idempotence, and the unresolvable-app degraded branch — verified to actually fail (3 errors, Class "OC" not found) when the try/catch is removed.

#138 also carries the spec delta (REQ-OBS-006), the e2e regression guard on the Bootstrap-aliased routes, and the gate-2/gate-46 fixes that touching this method pulls into diff scope. Matches the shape already merged in procest (#752) and scholiq (#288). Closing without merging; nothing here is lost.

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.

1 participant