[PR #264] [MERGED] Enforce module requires_core on load, install, update and update checks #175

Closed
opened 2026-10-04 12:06:19 +02:00 by thegame_1980 · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Vateron-Media/XC_VM/pull/264
Author: @Divarion-D
Created: 10/4/2026
Status: ✅ Merged
Merged: 10/4/2026
Merged by: @Divarion-D

Base: main ← Head: feat/module-requires-core


📝 Commits (3)

  • be115bd feat(modules): enforce requires_core when loading, installing and updating
  • 0ac6f08 docs(modules): document requires_core and its effect
  • e28dd50 Merge branch 'main' into feat/module-requires-core

📊 Changes

9 files changed (+433 additions, -20 deletions)

View changed files

📝 docs/en/development/module-authoring.md (+27 -1)
📝 src/Core/Module/ModuleLoader.php (+83 -2)
📝 src/Core/Module/ModuleManager.php (+65 -9)
📝 src/Core/Module/ModuleUpdateChecker.php (+51 -6)
📝 src/Public/Views/admin/modules.php (+1 -1)
➕ tests/Unit/ModuleCoreRequirementTest.php (+46 -0)
📝 tests/Unit/ModuleLoaderTest.php (+14 -0)
📝 tests/Unit/ModuleManagerMigrationsTest.php (+80 -1)
📝 tests/Unit/ModuleUpdateCheckerTest.php (+66 -0)

📄 Description

Why

requires_core in module.json was only read and shown in the Modules table. Nothing enforced it:

  • Update: a module release that needs a newer core could be installed or updated anyway. If it calls a core API this panel doesn't have, the panel goes down.
  • Update check: it offered the newest release even when this core can't run it.
  • Core rollback: modules left on disk that are newer than the rolled-back core kept loading.

What changes

ModuleLoader::coreRequirementError($requiresCore, $coreVersion = XC_VM_VERSION) returns null when the module fits, or the reason (needs core >=2.7.0; this panel runs 2.6.0).

Format. One or more conditions, all of which must hold, separated by spaces or commas:

  • operators >=, >, <=, <, =/==, !=;
  • a bare version means >=;
  • empty means any core, so existing modules keep working;
  • ^, ~, * and ranges are unreadable and refused (fail safe);
  • a nightly X.Y.Z-dev.N counts as X.Y.Z, since it is built from main as the upcoming release.

Where it applies

When Behaviour
Load (discoverModules) Skipped with a log line; dependents are skipped too (existing pruning). The panel stays up.
Install (placeModuleFiles: zip, LB fan-out, standard set, platform) Refused before the installed copy is touched. A refused copy that the platform extension already extracted into Modules/ is removed; the platform flow restores its backup as before.
git/url update (updateModuleFromSource) Checked right after the hash_id pin, before the backup.
Update check (ModuleUpdateChecker) git: reads module.json at the repository root of each newer tag from raw.githubusercontent.com (newest first, at most 10) and offers the first compatible release; none means nothing offered (not an error). An unreadable manifest still offers its tag, and the install re-checks the archive. url: honours requires_core in version.json.
Modules table The reason appears on the existing warning badge, relabelled "Issue(s)".

An LB whose core lags behind MAIN refuses the fan-out on its own; MAIN only queues the action (NodeActions::send), so its install is unaffected.

The checks live in small helpers, so discoverModules() (cc 7 → 6) and updateModuleFromSource() (cc 21 → 17) come out simpler.

Docs: a new Core compatibility section in docs/en/development/module-authoring.md.

Testing

  • New tests:
    • ModuleCoreRequirementTest covers 12 constraint cases, the reason text, and the default core version;
    • the loader skips a module for another core and its dependents;
    • a zip upload is refused and the installed copy stays;
    • a refused copy inside Modules/ is removed;
    • the git update check refuses another module (hash_id) and another core;
    • listModules shows the warning;
    • the update checker picks the newest compatible release, returns nothing when none fits, and still offers a tag whose manifest is unreadable; the url source is covered too.
  • Suite and gates: 2693 tests pass; make gates, make phpstan, phpcs and the strict make docs-build are clean. The CRAP gate passes (0 new or worsened functions).
  • Not tested on a live panel yet. Set "requires_core": ">=99.0" on a test module and check that:
    • the panel stays up and the module shows "Enabled" with an "Issue" badge and the reason;
    • the module is not loaded;
    • a zip upload of it is refused with a clear error, and the previous copy stays.

Not in this PR

  • Version constraints between modules, e.g. plex → watch >= 1.1.0. dependencies holds names only.
  • installModule() (the Install button for a module already on disk) does not re-check. That is harmless, because the loader won't load the module.

🤖 Generated with Claude Code

Summary by Sourcery

Enforce module core compatibility across the module lifecycle so incompatible modules are skipped, rejected, or withheld from update offers without affecting compatible installations.

New Features:

  • Enforce module core-version requirements when loading, installing, updating, and checking for updates.
  • Select compatible Git releases and honor core requirements from URL update manifests.
  • Display core incompatibility reasons in the Modules page issue warnings.

Bug Fixes:

  • Prevent modules built for newer cores from loading after installation or core rollback.
  • Prevent incompatible module updates from replacing working installed copies.

Enhancements:

  • Centralize core compatibility validation with support for multiple constraints, nightly versions, and fail-safe handling of unreadable formats.

Documentation:

  • Document supported requires_core constraints and module core compatibility behavior.

Tests:

  • Add coverage for core requirement parsing, loading and dependency pruning, installation and update rejection, module warnings, and compatible update selection.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Vateron-Media/XC_VM/pull/264 **Author:** [@Divarion-D](https://github.com/Divarion-D) **Created:** 10/4/2026 **Status:** ✅ Merged **Merged:** 10/4/2026 **Merged by:** [@Divarion-D](https://github.com/Divarion-D) **Base:** `main` ← **Head:** `feat/module-requires-core` --- ### 📝 Commits (3) - [`be115bd`](https://github.com/Vateron-Media/XC_VM/commit/be115bd0536da0428ed3b53e2266f77c10397519) feat(modules): enforce requires_core when loading, installing and updating - [`0ac6f08`](https://github.com/Vateron-Media/XC_VM/commit/0ac6f088349b71fdc629e383e66c6bd92ff6d972) docs(modules): document requires_core and its effect - [`e28dd50`](https://github.com/Vateron-Media/XC_VM/commit/e28dd50f3f6f5605ae4883c1a08dfe92c2c9aad4) Merge branch 'main' into feat/module-requires-core ### 📊 Changes **9 files changed** (+433 additions, -20 deletions) <details> <summary>View changed files</summary> 📝 `docs/en/development/module-authoring.md` (+27 -1) 📝 `src/Core/Module/ModuleLoader.php` (+83 -2) 📝 `src/Core/Module/ModuleManager.php` (+65 -9) 📝 `src/Core/Module/ModuleUpdateChecker.php` (+51 -6) 📝 `src/Public/Views/admin/modules.php` (+1 -1) ➕ `tests/Unit/ModuleCoreRequirementTest.php` (+46 -0) 📝 `tests/Unit/ModuleLoaderTest.php` (+14 -0) 📝 `tests/Unit/ModuleManagerMigrationsTest.php` (+80 -1) 📝 `tests/Unit/ModuleUpdateCheckerTest.php` (+66 -0) </details> ### 📄 Description ## Why `requires_core` in `module.json` was only read and shown in the Modules table. Nothing enforced it: - **Update:** a module release that needs a newer core could be installed or updated anyway. If it calls a core API this panel doesn't have, the panel goes down. - **Update check:** it offered the newest release even when this core can't run it. - **Core rollback:** modules left on disk that are newer than the rolled-back core kept loading. ## What changes `ModuleLoader::coreRequirementError($requiresCore, $coreVersion = XC_VM_VERSION)` returns `null` when the module fits, or the reason (`needs core >=2.7.0; this panel runs 2.6.0`). **Format.** One or more conditions, all of which must hold, separated by spaces or commas: - operators `>=`, `>`, `<=`, `<`, `=`/`==`, `!=`; - a bare version means `>=`; - empty means any core, so existing modules keep working; - `^`, `~`, `*` and ranges are unreadable and refused (fail safe); - a nightly `X.Y.Z-dev.N` counts as `X.Y.Z`, since it is built from main as the upcoming release. **Where it applies** | When | Behaviour | |---|---| | Load (`discoverModules`) | Skipped with a log line; dependents are skipped too (existing pruning). The panel stays up. | | Install (`placeModuleFiles`: zip, LB fan-out, standard set, platform) | Refused before the installed copy is touched. A refused copy that the platform extension already extracted into `Modules/` is removed; the platform flow restores its backup as before. | | git/url update (`updateModuleFromSource`) | Checked right after the `hash_id` pin, before the backup. | | Update check (`ModuleUpdateChecker`) | git: reads `module.json` at the repository root of each newer tag from raw.githubusercontent.com (newest first, at most 10) and offers the first compatible release; none means nothing offered (not an error). An unreadable manifest still offers its tag, and the install re-checks the archive. url: honours `requires_core` in `version.json`. | | Modules table | The reason appears on the existing warning badge, relabelled "Issue(s)". | An LB whose core lags behind MAIN refuses the fan-out on its own; MAIN only queues the action (`NodeActions::send`), so its install is unaffected. The checks live in small helpers, so `discoverModules()` (cc 7 → 6) and `updateModuleFromSource()` (cc 21 → 17) come out simpler. Docs: a new **Core compatibility** section in `docs/en/development/module-authoring.md`. ## Testing - **New tests:** - `ModuleCoreRequirementTest` covers 12 constraint cases, the reason text, and the default core version; - the loader skips a module for another core and its dependents; - a zip upload is refused and the installed copy stays; - a refused copy inside `Modules/` is removed; - the git update check refuses another module (`hash_id`) and another core; - `listModules` shows the warning; - the update checker picks the newest compatible release, returns nothing when none fits, and still offers a tag whose manifest is unreadable; the url source is covered too. - **Suite and gates:** 2693 tests pass; `make gates`, `make phpstan`, phpcs and the strict `make docs-build` are clean. The CRAP gate passes (0 new or worsened functions). - **Not tested on a live panel yet.** Set `"requires_core": ">=99.0"` on a test module and check that: - the panel stays up and the module shows "Enabled" with an "Issue" badge and the reason; - the module is not loaded; - a zip upload of it is refused with a clear error, and the previous copy stays. ## Not in this PR - Version constraints between modules, e.g. `plex` → `watch >= 1.1.0`. `dependencies` holds names only. - `installModule()` (the Install button for a module already on disk) does not re-check. That is harmless, because the loader won't load the module. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Summary by Sourcery Enforce module core compatibility across the module lifecycle so incompatible modules are skipped, rejected, or withheld from update offers without affecting compatible installations. New Features: - Enforce module core-version requirements when loading, installing, updating, and checking for updates. - Select compatible Git releases and honor core requirements from URL update manifests. - Display core incompatibility reasons in the Modules page issue warnings. Bug Fixes: - Prevent modules built for newer cores from loading after installation or core rollback. - Prevent incompatible module updates from replacing working installed copies. Enhancements: - Centralize core compatibility validation with support for multiple constraints, nightly versions, and fail-safe handling of unreadable formats. Documentation: - Document supported `requires_core` constraints and module core compatibility behavior. Tests: - Add coverage for core requirement parsing, loading and dependency pruning, installation and update rejection, module warnings, and compatible update selection. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
thegame_1980 added the pull-request label 2026-10-04 12:06:19 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Vateron-Media/XC_VM#175