The LB strip lists still named files by their pre-PSR-4 www/ paths, so
those entries matched nothing and the files kept shipping under Public/:
Public/admin/api.php, Public/admin/proxy_api.php, Public/stream/auth.php
and Public/stream/probe.php reached every LB. auth.php and probe.php are
routed by the LB nginx but fatal there on the stripped Domain/User.
LB_DIRS still listed the removed resources/ and www/ trees, and
LB_DIRS_TO_REMOVE listed the non-existent Domain/Auth. The gate never
noticed, because it only checked the SENSITIVE prefixes.
Makefile:
- Drop the dead entries (resources, www, Domain/Auth, GeoLite2-City.mmdb,
the nine www/* files).
- Strip the MAIN-only files that no LB route or LB code needs: the four
endpoints above, PlayerV2, AdminAPIWrapper, ResellerAPIWrapper,
ActiveCodeApiController, ResellerApiDispatcher, ResellerTableRenderer,
Cli/migration_logic.php and DbMigrateCommand (on an LB it would run
MAIN's schema migrations). The viewer-API controllers still ship
because the LB nginx still routes them; they go later with the routes.
- lb_delete_files_list now always writes the LB deleted_files.txt as the
LB-scoped git deletions plus every stripped code file (Cli, Core,
Domain, Infrastructure, Public, Streaming; never bin/config/content).
An update only deletes listed files, so without this the files newly
stripped here would stay on LBs that are already installed. The
retired resources/ and www/ trees stay in the git-deletion scope
(LB_RETIRED_DIRS), so their old entries are still applied.
lb_configs/nginx.conf: stop routing /stream/auth, /stream/probe and
/probe.php. They now get nginx's 404 instead of a PHP fatal. MAIN nginx
is unchanged.
verify-lb-archive.sh now also fails when:
- an LB_* entry matches no tracked path (STALE);
- a script the LB nginx executes is stripped (MISSING): every
SCRIPT_FILENAME, and Public/<scope>/<handler>.php for each handler the
/stream/ and /admin/ gateway locations accept.
SENSITIVE covers the new strips, CacheHandlerCommand and
CacheEngineCronJob. The manifest lookups now use here-strings, because
under pipefail an early `grep -q` exit could SIGPIPE the writer and hide
a leak. LbArchiveGateTest runs the real script with each failure mode
injected through MAKEFLAGS.
Behaviour: the LB archive loses 26 files that no LB route reaches (or
that fatal there today), and the next LB update deletes them. It also
deletes any lingering Domain/User or Domain/Device files. Fresh LBs
never had those, so an older LB that still carries them loses rtmp and
viewer-API paths that only worked because of them, just like a fresh LB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQHeypQpfAXTdCysmi7b6a
Load balancers added before provisioning shipped config/openssl_extra
run on the built-in OPENSSL_EXTRA while their main holds a random one,
so they reject every token the main mints for them. server:diagnose now
shows this; nothing could fix it short of a manual copy.
server:sync-openssl-extra <server_id>|--all [--force], run on the main,
queues one root signal carrying the main's value for each streaming LB
whose published fingerprint differs. Nodes that publish none (not
updated), offline nodes and nodes already in sync need --force; proxies
and the main are never sent it. A main without config/openssl_extra
runs on the default like its LBs, and the command sends nothing.
The LB's cron:root_signals handles 'set_openssl_extra' in its own case
(ignored on the main, whose value keys hmac_keys and image names) with
OpensslExtra::install: it writes the value it replaces to
openssl_extra.prev, accepted for 600 s by Encryption::readToken, then
swaps openssl_extra in through a 0600 temporary file owned like
config/, so php-fpm never sees a file it cannot read. php-fpm picks the
new value up on its next request. Installing the value already in use
keeps an earlier .prev. server:install now also clears a stale .prev,
and server:diagnose names the command in its mismatch report.
The value sits in the signals table until the LB's root cron deletes the
row, about a minute; a legacy LB reads that database in full anyway.
The command is MAIN-only: it is stripped from the LB archive and listed
in verify-lb-archive.sh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQHeypQpfAXTdCysmi7b6a
The skip list keyed on /Modules/tmdb/lib/, which matches nothing: there is no
src/Modules/tmdb* dir, and the vendored non-namespaced TMDB library lives at
src/Infrastructure/Tmdb/lib/. So the intended skip was dead and that library
was being scanned. Point the skip at the real path. Gate still passes
(411 migrated classes, no violations).
Belt-and-suspenders so dev packages can never land in git:
- .gitignore: whitelist the committed production set under src/vendor/ and ignore
everything else (src/vendor/* + !autoload.php/!chrisyue/!composer/!gemorroj,
then re-ignore composer/{pcre,semver,xdebug-handler,autoload_files.php}). A
local `composer install` (for PHPStan/PHP-CS-Fixer) now writes dev packages
into ignored paths, so `git add` can never stage them. gitignore never
untracks, so the committed prod files stay tracked and updatable; a NEW prod
dep just needs a `!` whitelist line.
- check-vendor-prod-only.sh: add a second check on the committed
vendor/composer/installed.json — it must list no dev package. This covers the
one thing .gitignore cannot: installed.json is a tracked file that
`composer install` rewrites with dev entries. Both checks read the git INDEX,
so a local dev install does not trip the gate, but a committed dev artifact does.
Verified: clean tree passes; a dev `composer install` in the working tree leaves
the gate green (index-based); a fabricated dev installed.json is detected; no dev
dir is stageable after `composer install`.
Switch from "commit vendor with dev deps + strip at release" to the standard
application model: the committed src/vendor/ is PRODUCTION-ONLY, and dev tooling
(PHPStan, PHP-CS-Fixer + ~37 transitive deps) is installed on demand with
"composer install".
- Regenerate the committed src/vendor/ via "composer install --no-dev"
(34 MB -> ~0.5 MB; only the Composer autoloader + gemorroj/m3u-parser +
chrisyue/php-m3u8 remain). This also stops PHPStan\PharAutoloader registering
in production.
- Commit src/composer.lock (un-ignored) — this is an application, so the lock is
committed to make "composer install" reproducible across dev/CI.
- Revert the release-time strip step (Makefile hooks + tools/build/
strip-dev-vendor.sh) — no longer needed; the archive ships the prod vendor as-is.
- CI: the phpstan and code-style jobs now run "composer install --working-dir=src"
(with tools: composer) to obtain the dev tools before running.
- New gate tools/ci/check-vendor-prod-only.sh (+ make check-vendor-prod-only,
wired into "make gates"): asserts no require-dev package from composer.lock is
committed under src/vendor/ — guards against accidentally committing a
dev-bloated vendor. Inspects git-tracked files, so it is correct even in a CI
job that already ran "composer install".
- Fix verify-lb-archive.sh: LB legitimately ships most of Cli/Commands and
Cli/CronJobs (edge commands + certbot/cache/cleanup crons), so flag only the
genuinely privileged dirs + the specific install/root files, not the whole dirs.
- .gitignore / composer.json notes updated.
Verified before pruning: PHPStan no errors, PHPUnit 303, cs + gates green. After
pruning: PHPUnit 303 (prod-only vendor), all three gates green. Local dev tools
restored afterwards with "composer install" (not committed).
Adds the automated gates the PSR-4 plan specified but that were verified only
manually per phase:
PHPUnit (run by the existing test job):
- AutoloadOrderTest — Composer autoloader registered; the retired
XC_Autoloader scanner is NOT in the SPL stack; init()
is a no-op; no igbinary class-map cache is written.
- BootstrapPathsTest — no live require/include points at a lowercase renamed
dir (the Фаза-1 grep-gate, as a runtime guard).
- ConsoleDiscoveryTest — console.php FQCN discovery resolves every Cli command
file and the concrete command surface stays stable.
Shell gates (new 'PSR-4 Regression Gates' CI job + 'make gates'):
- tools/ci/check_procedural_use.php — procedural/view files must import every
migrated class they use, with the `use` ABOVE the usage (PHP imports are
positional). Runs with short_open_tag=1 so short-tag templates are analysed.
- tools/ci/verify-lb-archive.sh — reproduces the Makefile LB file selection from
the real LB_* vars and asserts no privileged tree (Admin/Reseller/Player
controllers, Domain/User|Device, Cli/CronJobs|Commands) ships to an LB node
(security blocker 1).
Makefile: cs/cs-fix now force short_open_tag=1; new print-%, check-procedural-use,
verify-lb-archive and aggregate `gates` targets.