A single Paths value object holds every filesystem location the engine needs: configuration, data, modules, cache
and logs. Bootstrap constructs it once, from the arguments the panel is booted with, and it replaces the
configuration component’s ConfigPaths.
The configuration loader receives its locations through ConfigPaths, which holds configuration locations and
nothing else. More components need locations of their own: modules, the cache and logs. A single object holding
every location, constructed once during early bootstrap, is cleaner than a paths object for each component.
| Term | Meaning |
|---|---|
| Root | An absolute directory under which the engine keeps one type of file. |
| Relative path | A path beneath a root, joined onto it by Paths. |
| Component | Responsibility |
|---|---|
Paths |
Final readonly value object holding every root, and joining relative paths onto them. |
| Property | Holds |
|---|---|
config |
The configuration directory, holding config.toml, config.d/ and modules-enabled/. |
data |
The application data directory. |
modules |
The Composer project that external modules are installed into, per ADR-0012. |
cache |
The cache directory. |
logs |
The log directory. |
Each root is an absolute path. Paths holds the roots and does nothing else with them: it does not work out where
they are, check that they exist, or create them.
$paths = new Paths(
config: '/etc/tgp',
data: '/var/lib/tgp',
modules: '/var/lib/tgp/modules',
cache: '/var/lib/tgp/cache',
logs: '/var/log/tgp',
);
Each root has a method of the same name that joins a relative path onto it:
| Method | Returns |
|---|---|
config(string $path): string |
The path beneath the configuration directory. |
data(string $path): string |
The path beneath the data directory. |
modules(string $path): string |
The path beneath the modules directory. |
cache(string $path): string |
The path beneath the cache directory. |
logs(string $path): string |
The path beneath the log directory. |
The root’s trailing separator and the path’s leading separator are removed, and the two are joined with a single
separator. A path passed to a method is always treated as relative to the root, so $paths->config('config.d') and
$paths->config('/config.d') both return /etc/tgp/config.d.
Bootstrap constructs Paths once, during early bootstrap, before configuration is loaded or modules are discovered.
Its roots come from arguments given when the panel is booted, which are passed in during bootstrapping.
TomlLoader::load() takes Paths in place of ConfigPaths, and reads from the configuration directory:
config.toml, config.d/ and modules-enabled/ beneath $paths->config. ConfigPaths is removed.
$tree = new TomlLoader()->load($paths);
A paths object for each component, of which ConfigPaths is the existing one. Once modules, the cache and logs
need locations as well as configuration, a single object holding every root, constructed once during early
bootstrap, is cleaner.
No other alternatives were weighed.
ConfigPaths is removed, and TomlLoader::load() takes Paths instead.config.toml in the configuration directory, and the drop-in and module
configuration directories are always config.d/ and modules-enabled/ beside it. ConfigPaths held each of the
three as a path of its own.$paths->config. This is created. Its tasks, ticked on
2026-07-04, include resolving the roots in layers from compiled defaults, environment variables and command-line
flags, and testing that resolution. Nothing at a50a9ab resolves the roots, and that resolution is left to
bootstrapping.decided. Paths, its methods and TomlLoader’s use of it are described from
src/Config/Paths.php at a50a9ab,
src/Config/TomlLoader.php at a50a9ab
and
tests/Unit/Config/ConfigPathsTest.php at a50a9ab.