# 0001 — Own the module kernel rather than take a package

**Status:** Accepted · 2026-08-01 · Supersedes nothing

## Context

The product must support installing a module (Pharmacy, Laboratory, Accounting)
onto a running clinic without touching any existing module. Clinics run on their
own cPanel accounts. There is no shell, no Composer, and no Node.

`nwidart/laravel-modules` is the obvious off-the-shelf answer. It resolves module
namespaces through `wikimedia/composer-merge-plugin` — that is, at
`composer dump-autoload` time.

Our release archives ship a pre-built `vendor/` with an optimised, authoritative
classmap. An authoritative classmap deliberately refuses to look for classes it
has never seen. So a module directory dropped in by the auto-updater would not
autoload, and the one command that would fix it is the one we cannot run on a
customer's server.

Licence gating, install/enable/disable/uninstall hooks, dependency ordering and
"blocked with a reason instead of a 500" would all have had to be layered on top
regardless.

## Decision

Build the module kernel: `app/Modules/` — manifest, repository with a compiled
cache, status store, manager, and a **runtime PSR-4 autoloader** registered after
Composer's, which resolves only what Composer could not.

Core modules are resolved and registered before optional ones. That is not
tidiness: the Licensing module is itself a module, and it supplies the
`FeatureGate` that decides whether the optional modules may boot. Booting core
first is what breaks that circle.

## Consequences

**Good.** A module dropped in by the updater works with no Composer step, on any
host, regardless of how the release was built. Licence gating, lifecycle hooks
and dependency resolution are ours to shape. A missing dependency or a lapsed
licence blocks a module with a message an administrator can read, instead of a
white screen a receptionist cannot debug.

**Bad.** Roughly 400 lines of infrastructure we maintain, and a scaffolding
command whose stubs must stay in step with the architecture. Mitigated by
treating `module:make` as the contract made executable — the generated module is
the reference implementation, and the architecture tests fail the build if a
module drifts from it.

**Cost of reversal.** High once several modules exist. This is a foundation
decision, made deliberately and early.
