Skip to content

docs(apphost): correct the laziness claim and document the mandatory autoload prelude - #2358

Open
rubenvdlinde wants to merge 3 commits into
developmentfrom
docs/apphost-autoload-prelude
Open

docs(apphost): correct the laziness claim and document the mandatory autoload prelude#2358
rubenvdlinde wants to merge 3 commits into
developmentfrom
docs/apphost-autoload-prelude

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Documentation only. No behaviour change, no code change.

The claims that are wrong as written

lib/AppHost/Bootstrap.php:

Lazy by construction — a disabled OpenRegister never fatals NC bootstrap

With OpenRegister disabled/absent, Application::register() (and this method) complete without loading a single OR class, so Nextcloud boots.

lib/AppHost/Routes.php:

This file references no OCA\OpenRegister\… symbol — it is a pure array builder, so requiring it from a leaf routes.php is safe even when OpenRegister is disabled.

Both are true of the closure bodies and of these files' contents. Neither is true of resolving Bootstrap / Routes themselves. To call Bootstrap::register() the leaf must resolve the symbol OCA\OpenRegister\AppHost\Bootstrap, and that is an ordinary autoload. If the prefix is unavailable, the call throws before a single closure has been created — the array builder being pure does not help, because control never reaches the array.

Why that matters more than it looks

OC_App::getEnabledApps() does sort($apps), and Coordinator::registerApps() walks that sorted list calling OC_App::registerAutoloading($appId, $path) and then $application->register() for one app at a time. So an app's register() runs before the PSR-4 prefix of every alphabetically-later app exists.

Scope, stated precisely: this affects AppHost consumers whose app id sorts before openregister — on a healthy instance, with OpenRegister enabled. Apps sorting after are unaffected today; for them the prelude is defensive only. Observed on doriath (audit listener recorded ZERO dispatched events after an unguarded reference aborted the whole register()) and independently on openconnector (class_exists at register(): false on a clean install). openbuild is hit in CI.

Guarded, it degrades quietly: class_exists() answers FALSE, the plumbing is skipped, and classes existing ONLY as aliases registered by Bootstrap — a leaf's Controller\HealthController aliasing AppHost\Controller\GenericHealthController — then fail to resolve, so those endpoints return 500, not 404. Unguarded, the \Error aborts the leaf's entire register(); Coordinator logs an emergency and continues, so the app stays enabled with half its wiring missing.

What this PR changes

  1. Bootstrap.php — replaces the "Lazy by construction" section with one that separates lazy bodies from non-lazy resolution, documents the sort-order trap, and specifies the required prelude, including why IAppManager::loadApp() (it calls Coordinator::bootApp(), booting OR before its own register() has run) and a relative include_once ../../../openregister/vendor/autoload.php (assumes one shared apps directory; silently no-ops on multi-apps_paths) are both wrong.
  2. Routes.php — corrects the safety claim and shows the class_exists()-guarded canonical form. Also notes that route files are loaded during request matching, long after every app has registered, so routes.php is not exposed to the registration-order trap — a bare unguarded return Routes::standard() is simply a hard dependency on OpenRegister being installed and enabled.
  3. openspec/specs/apphost-boilerplate/spec.md — the "One-Call Bootstrap" requirement carried the same overclaim ("lazily, such that Nextcloud bootstrap never fatals when OpenRegister is disabled"); its "Disabled OpenRegister degrades, never fatals" scenario is only true for a leaf that guards the call, so the GIVEN now says so. Adds a Requirement: Autoload Prelude for AppHost Adoption with two scenarios.

The Bootstrap.php invariant note is kept but narrowed: no OR symbol outside a closure is what keeps the closures lazy, and it says nothing about resolving Bootstrap itself, which is the leaf's responsibility.

Verification

php -l clean on both files. PHPCS clean on both (run under the container's PHP 8.4 with openregister's own ruleset). I checked that no test reads these files as text before adding OCA\OpenRegister\… names into their docblocks — tests/Unit/AppHost/BootstrapTest.php asserts behaviour, not source text (positive-controlled: a control pattern matched the same file 8×).

Enforcement of the prelude across adopting leaves is hydra gate-64 (ConductionNL/.github#174). Leaf-side fixes: doriath#162, opencatalogi#812, openconnector#1157, docudesk#390, launchpad#58, pipelinq#711.


[hydra-gate-security-change-has-tests exclude] Documentation-only diff: two docblocks under lib/AppHost/ and one openspec spec file. gate-47 classifies Bootstrap.php as security-touching because the FILE CONTAINS matching tokens, not because this diff changes any of them — no executable statement is added, removed or altered, so there is nothing for a test to exercise.

…relude

Bootstrap.php claimed 'Lazy by construction — a disabled OpenRegister never
fatals NC bootstrap', and Routes.php claimed that referencing no OR symbol makes
requiring it 'safe even when OpenRegister is disabled'. Both hold for the
closure BODIES, not for resolving Bootstrap/Routes themselves: calling either is
an ordinary autoload of an OCA\OpenRegister\ class, and it throws when that
prefix is unavailable.

That is the normal case for leaves sorting before openregister, because
getEnabledApps() sort()s the app list and Coordinator::registerApps() calls
registerAutoloading() then register() one app at a time.

Documents the required autoload prelude as part of AppHost adoption, records why
loadApp() and a relative vendor/autoload include are both wrong, and adds the
matching requirement + scenarios to the canonical apphost-boilerplate spec.

Documentation only — no behaviour change.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 4d84e0f

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
composer ✅ 173/173
npm ✅ 713/713
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-05 21:26 UTC

Download the full PDF report from the workflow artifacts.

… diff

[hydra-gate-security-change-has-tests exclude] Documentation-only diff: two
docblocks under lib/AppHost/ and one openspec spec file. gate-47 classifies
Bootstrap.php as security-touching because the FILE CONTAINS matching tokens,
not because this diff changes any of them — no executable statement is added,
removed or altered, so there is nothing for a test to exercise. Recorded as an
empty commit because the gate reads the head commit message, and a re-run
replays the original event payload rather than an edited PR body.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 58c81eb

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
composer ✅ 173/173
npm ✅ 713/713
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-05 21:45 UTC

Download the full PDF report from the workflow artifacts.

…he PR body

The previous opt-out lived only in the commit message. On a pull_request event
actions/checkout checks out the refs/pull/N/merge ref, so the gate's
`git log -1 --pretty=%B` reads GitHub's merge commit, not ours, and never sees
it. HYDRA_GATE_PR_BODY is the path that works — and a re-run replays the
original event payload, so a fresh push is needed for the edited body to be
picked up.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 60f1dca

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
composer ✅ 173/173
npm ✅ 713/713
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-05 22:03 UTC

Download the full PDF report from the workflow artifacts.

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