The multi-line grouping parens in the XC_VM_BUILD_ID define (added in
c289352d) tripped Generic.WhiteSpace.ArbitraryParenthesesSpacing, breaking
the green `make cs` baseline. Reflow as a multi-line define() call with the
`?:` grouping paren kept tight. Behaviour unchanged (still 'dev' in a source
checkout). `make cs` green again.
`make main`/`make lb` now stamp a unique per-build id — version + short git
SHA + UTC timestamp + 8 random bytes — into RELEASE_ID at the staged deploy
root (new stamp_release_id target, run after the copy step; the file is
generated per build, never git-tracked). ConstantsInitializer exposes it at
file scope as XC_VM_BUILD_ID by reading RELEASE_ID relative to __DIR__; a
source/dev checkout has no file and reports 'dev'.
This gives every build a traceable fingerprint so a leaked or rebranded copy
can be tied back to its origin (and, once activation ships, reported in the
phone-home alongside install_id). Kept at file scope to avoid adding a branch
to ConstantsInitializer::init() (CRAP ratchet). README discloses it.
Tests: XC_VM_BUILD_ID defined and defaults to 'dev' in a source checkout.
Full suite (817) + gates + CRAP + phpcs green; Makefile targets validated.
XC_VM_VERSION / DEV_MODE / DB_ACCESS_ENABLED / DB_ACCESS_PWD are edited on every
release (and by the release automation). Buried as array entries in appConfig()
they were awkward to find and to sed. Move them back to guarded define()s at the
top of ConstantsInitializer.php; appConfig() reads them back, and init() skips
the already-defined ones. The guard keeps a pre-definition (e.g. the PHPStan
stub) from fataling.
Update the release checklist accordingly: the sed commands target the familiar
`define('XC_VM_VERSION', '...')` form in ConstantsInitializer.php again (they were
pointing at the deleted AppConfig.php).
The Paths/AppConfig/Binaries shims were byte-identical (each just called
ConstantsInitializer::init()), and ErrorCodes.php only bridged a $rErrorCodes
global that nothing reads any more. Every boot path required all five by name.
Replace those requires with a single ConstantsInitializer::init() at each of the
four call sites (ConstantsStage, WebApiBootstrap, StreamingRequestBootstrap, the
progress endpoint) and delete the four dead shim files. The generateError()/
generate404() functions are already provided globally via composer autoload.files,
so their require was redundant too — ErrorHandler.php stays (it is the
autoload.files target) but is no longer required by name.
Verified: init() defines the full constant set and the error functions autoload;
762 tests, PHPStan, phpcs and make gates green; all four boot smokes (cli/admin/
webapi/streaming) still PASS against a real MariaDB in the dev container.
OPENSSL_EXTRA was a hardcoded literal shared by every install, i.e. public in the
source tree. Give fresh installs their own secret while leaving existing installs
untouched.
- Installer: after writing config.ini, generate a 40-char secret into
config/openssl_extra (chmod 600), but ONLY when the file is absent. It is key
material for Encryption (hmac_keys rows, cached image filenames, proxy URL keys,
stream tokens), so it must be generated once and never rotated — re-running the
installer must not overwrite it or all existing encrypted data would orphan.
- ConstantsInitializer now sources OPENSSL_EXTRA from that file directly rather
than through the config extension. A standalone file is deliberate: it is
independent of the config.ini -> config.enc migration and never rewritten, so
the value is read back identically for the life of the install regardless of
how the proprietary extension surfaces config keys. Missing/empty file falls
back to the historical literal, so existing installs keep decrypting unchanged.
Verified: no file -> literal, file -> its value, empty file -> literal; installer
generation is idempotent (40 chars, 0600, not overwritten on re-run).
Introduce the source-of-truth classes for the bootstrap testability refactor.
Purely additive — nothing is wired to them yet.
- ConstantsInitializer: pure value maps (paths/appConfig/binaries/statuses)
plus the single define() site (init/initStatus). The maps evaluate with
different MAIN_HOME/BIN_PATH in one process, which the one-shot define()
constants they feed cannot — this is what makes them testable.
- ErrorResponder: the generateError()/generate404() logic extracted into pure
codes()/renderDebug()/render404()/respond*() plus a single side-effecting
emit(). A test-mode toggle throws ErrorResponseException instead of exit().
- ErrorResponseException: value carrier for a resolved error response.
OPENSSL_EXTRA is now sourced per-install via ConfigReader with a mandatory
fallback to the historical literal, so existing installs (whose persisted data
derives from it) keep decrypting; generation-at-install is left to the installer.
Verified byte-for-byte against the legacy prelude before wiring: 53/53 constants,
debug/404 HTML frozen as sha256 goldens, 65 error codes.