{
    "generated_at": "2026-11-06T15:35:14+01:00",
    "duration_ms": 2062,
    "overall": "fail",
    "summary": {
        "ok": 46,
        "warn": 11,
        "fail": 1,
        "info": 15
    },
    "groups": {
        "PHP runtime": [
            {
                "key": "php.version",
                "label": "PHP version",
                "status": "warn",
                "value": "8.1.2-1ubuntu2.28",
                "explanation": "Runs Laravel 10, which is end of life with unpatched advisories. Laravel 11/12 need PHP 8.2+, and the hosting offers no version choice."
            },
            {
                "key": "php.sapi",
                "label": "SAPI",
                "status": "info",
                "value": "fpm-fcgi",
                "explanation": "Served by PHP-FPM, as on a WAYHOST php site."
            },
            {
                "key": "php.user",
                "label": "Process user",
                "status": "info",
                "value": "mbankole3 (uid 1006)",
                "explanation": "The pool runs as the hosting account, so the app can write wherever the account can."
            },
            {
                "key": "php.extensions.required",
                "label": "Required extensions",
                "status": "ok",
                "value": "ctype, curl, dom, fileinfo, filter, gd, hash, json, mbstring, openssl, pcre, pdo, pdo_mysql, session, tokenizer, xml",
                "explanation": "Everything Laravel 10 and this app need is loaded."
            },
            {
                "key": "php.extensions.bcmath",
                "label": "Extension bcmath",
                "status": "warn",
                "value": "missing",
                "explanation": "Money/decimal maths (brick/money, many payment SDKs) and some UUID libraries need it."
            },
            {
                "key": "php.extensions.intl",
                "label": "Extension intl",
                "status": "warn",
                "value": "missing",
                "explanation": "Number/date localisation, Str::of()->...->ucwords on some locales, Laravel 11 Number helper."
            },
            {
                "key": "php.extensions.zip",
                "label": "Extension zip",
                "status": "ok",
                "value": "loaded",
                "explanation": "Excel/zip exports (maatwebsite/excel, phpspreadsheet)."
            },
            {
                "key": "php.extensions.exif",
                "label": "Extension exif",
                "status": "ok",
                "value": "loaded",
                "explanation": "Reading photo orientation (intervention/image) before resizing."
            },
            {
                "key": "php.extensions.redis",
                "label": "Extension redis",
                "status": "warn",
                "value": "missing",
                "explanation": "phpredis for cache/queue/session on Redis; the hosting has no Redis server either."
            },
            {
                "key": "php.extensions.pdo_sqlite",
                "label": "Extension pdo_sqlite",
                "status": "warn",
                "value": "missing",
                "explanation": "SQLite databases, used by many test setups and small apps."
            },
            {
                "key": "php.extensions.sodium",
                "label": "Extension sodium",
                "status": "ok",
                "value": "loaded",
                "explanation": "Modern crypto (argon2id via libsodium, paseto, some JWT libs)."
            },
            {
                "key": "php.extensions.pcntl",
                "label": "Extension pcntl",
                "status": "warn",
                "value": "missing",
                "explanation": "Signals for queue:work timeouts; never available under FPM anyway."
            },
            {
                "key": "php.extensions.imagick",
                "label": "Extension imagick",
                "status": "warn",
                "value": "missing",
                "explanation": "Higher quality image processing; GD is the only option here."
            },
            {
                "key": "php.extensions.loaded",
                "label": "All loaded extensions",
                "status": "info",
                "value": "Core, date, libxml, openssl, pcre, zlib, filter, hash, json, Reflection, SPL, session, standard, sodium, cgi-fcgi, mysqlnd, PDO, xml, calendar, ctype, curl, dom, mbstring, FFI, fileinfo, ftp, gd, gettext, iconv, exif, mysqli, pdo_mysql, Phar, posix, readline, shmop, SimpleXML, sockets, sysvmsg, sysvsem, sysvshm, tokenizer, xmlreader, xmlwriter, xsl, zip, Zend OPcache",
                "explanation": "What this pool actually has, for comparison with other hosts."
            },
            {
                "key": "php.open_basedir",
                "label": "open_basedir",
                "status": "ok",
                "value": "/home/mbankole3/web:/tmp",
                "explanation": "The project is inside it. Anything outside (e.g. /usr/bin/php for the scheduler, /etc, /proc) is invisible to PHP."
            },
            {
                "key": "php.disable_functions",
                "label": "disable_functions",
                "status": "ok",
                "value": "(none)",
                "explanation": "symlink(), proc_open() and set_time_limit() are available."
            },
            {
                "key": "php.opcache",
                "label": "OPcache",
                "status": "ok",
                "value": "enabled",
                "explanation": "Compiled PHP is cached between requests."
            },
            {
                "key": "php.memory_limit",
                "label": "memory_limit",
                "status": "ok",
                "value": "256M",
                "explanation": "Enough for Laravel and a GD thumbnail of a large photo."
            },
            {
                "key": "php.max_execution_time",
                "label": "max_execution_time",
                "status": "warn",
                "value": "60s",
                "explanation": "Long jobs run inside web requests here (no worker, no cron). /_ops raises it with set_time_limit(); nginx still cuts at fastcgi_read_timeout (60s by default)."
            },
            {
                "key": "php.upload_max_filesize",
                "label": "upload_max_filesize / post_max_size",
                "status": "ok",
                "value": "64M / 64M",
                "explanation": "Covers the app's own image limit (4096 KB)."
            },
            {
                "key": "php.user_ini",
                "label": "user_ini.filename",
                "status": "info",
                "value": ".user.ini",
                "explanation": "If set, a .user.ini in the site root can raise upload_max_filesize / memory_limit (PHP_INI_PERDIR values), not open_basedir."
            }
        ],
        "Application": [
            {
                "key": "app.framework",
                "label": "Laravel",
                "status": "warn",
                "value": "10.50.4",
                "explanation": "Laravel 10 stopped receiving security fixes in February 2025. Every 10.x has open advisories, so Composer refuses it unless they are ignored explicitly (see composer.json). Upgrading needs PHP 8.2+."
            },
            {
                "key": "app.key",
                "label": "APP_KEY",
                "status": "ok",
                "value": "set",
                "explanation": "Cookies, sessions and encrypted values work."
            },
            {
                "key": "app.debug",
                "label": "APP_DEBUG",
                "status": "ok",
                "value": "false",
                "explanation": "Errors are logged, not displayed."
            },
            {
                "key": "app.env",
                "label": "APP_ENV",
                "status": "info",
                "value": "production",
                "explanation": "migrate and db:seed need --force outside \"local\"; /_ops passes it."
            },
            {
                "key": "app.url",
                "label": "APP_URL",
                "status": "ok",
                "value": "https://mbankole3-2.shared-fi01-001.hosting.wayhost.cloud",
                "explanation": "Matches the address this page was requested on."
            },
            {
                "key": "app.asset_url",
                "label": "ASSET_URL",
                "status": "warn",
                "value": "https://mbankole3-2.shared-fi01-001.hosting.wayhost.cloud",
                "explanation": "The hosting serves the project root, not public/. Without ASSET_URL=<APP_URL>/public, CSS and images 404."
            },
            {
                "key": "app.document_root",
                "label": "Document root",
                "status": "info",
                "value": "/home/mbankole3/web/mbankole3-2.shared-fi01-001.hosting.wayhost.cloud/public",
                "explanation": "Points at public/: the safe layout."
            },
            {
                "key": "app.caches",
                "label": "Config / route / view caches",
                "status": "info",
                "value": "config cached, routes cached, events not cached",
                "explanation": "Without SSH, caches are built and cleared from /_ops. A cached config ignores later .env edits until \"config:cache\" runs again."
            }
        ],
        "Filesystem": [
            {
                "key": "fs.storage/app/public",
                "label": "storage/app/public",
                "status": "ok",
                "value": "writable",
                "explanation": "Owned by the account the pool runs as."
            },
            {
                "key": "fs.storage/framework/cache",
                "label": "storage/framework/cache",
                "status": "ok",
                "value": "writable",
                "explanation": "Owned by the account the pool runs as."
            },
            {
                "key": "fs.storage/framework/sessions",
                "label": "storage/framework/sessions",
                "status": "ok",
                "value": "writable",
                "explanation": "Owned by the account the pool runs as."
            },
            {
                "key": "fs.storage/framework/views",
                "label": "storage/framework/views",
                "status": "ok",
                "value": "writable",
                "explanation": "Owned by the account the pool runs as."
            },
            {
                "key": "fs.storage/logs",
                "label": "storage/logs",
                "status": "ok",
                "value": "writable",
                "explanation": "Owned by the account the pool runs as."
            },
            {
                "key": "fs.bootstrap/cache",
                "label": "bootstrap/cache",
                "status": "ok",
                "value": "writable",
                "explanation": "Owned by the account the pool runs as."
            },
            {
                "key": "fs.env",
                "label": ".env",
                "status": "ok",
                "value": "present, writable",
                "explanation": "key:generate can store the key."
            },
            {
                "key": "fs.storage_link",
                "label": "public/storage",
                "status": "ok",
                "value": "symlink -> /home/mbankole3/web/mbankole3-2.shared-fi01-001.hosting.wayhost.cloud/storage/app/public",
                "explanation": "Uploaded images are served from storage/app/public."
            },
            {
                "key": "fs.disk_free",
                "label": "Free space on the filesystem",
                "status": "info",
                "value": "168.9 GB",
                "explanation": "Filesystem-wide figure. The account quota is lower and PHP cannot read it."
            },
            {
                "key": "fs.tmp",
                "label": "Temporary directory",
                "status": "ok",
                "value": "/tmp",
                "explanation": "Uploads and Symfony temp files land here (allowed by open_basedir)."
            }
        ],
        "Database": [
            {
                "key": "db.connection",
                "label": "Connection",
                "status": "ok",
                "value": "mysql://mbankole3_shop@localhost (socket /run/mysqld/mysqld.sock)/mbankole3_shop",
                "explanation": "Connected in 3.3 ms (Localhost via UNIX socket)."
            },
            {
                "key": "db.server",
                "label": "Server version",
                "status": "info",
                "value": "5.5.5-10.6.23-MariaDB-0ubuntu0.22.04.1",
                "explanation": "MariaDB 10.6 is what a WAYHOST node installs."
            },
            {
                "key": "db.migrations",
                "label": "Migrations",
                "status": "ok",
                "value": "7 applied, 0 pending",
                "explanation": "Schema is up to date."
            },
            {
                "key": "db.limits",
                "label": "Per-user limits",
                "status": "info",
                "value": "max_user_connections=30, max_statement_time=60s",
                "explanation": "The hosting caps each database user (10 connections, 60 s per statement by default). With pm.max_children=10, 10 busy workers use every connection."
            }
        ],
        "Queue and scheduler": [
            {
                "key": "queue.driver",
                "label": "Queue driver",
                "status": "info",
                "value": "database",
                "explanation": "Jobs wait in a table until a worker runs. The hosting runs none: /_ops \"queue:work\" is the worker."
            },
            {
                "key": "queue.pending",
                "label": "Pending jobs",
                "status": "ok",
                "value": "0",
                "explanation": "Nothing waiting."
            },
            {
                "key": "queue.failed",
                "label": "Failed jobs",
                "status": "ok",
                "value": "0",
                "explanation": "No job has exhausted its retries."
            },
            {
                "key": "queue.last_processed",
                "label": "Last job processed",
                "status": "info",
                "value": "2 h ago (App\\Domain\\Orders\\Jobs\\SendOrderConfirmation)",
                "explanation": "Recorded by a Queue::after listener."
            },
            {
                "key": "scheduler.heartbeat",
                "label": "Scheduler heartbeat",
                "status": "ok",
                "value": "10s ago via cli, previous beat 60s earlier",
                "explanation": "A system cron drives the scheduler every minute."
            },
            {
                "key": "scheduler.php_binary",
                "label": "PHP CLI binary seen from PHP",
                "status": "fail",
                "value": "not found",
                "explanation": "From FPM under open_basedir, PHP cannot see /usr/bin/php, so schedule:run builds \"'' artisan ...\" and every ->command() event fails. /_ops uses an in-process runner instead."
            }
        ],
        "Cache, session, mail, logs": [
            {
                "key": "cache.rw",
                "label": "Cache (file)",
                "status": "ok",
                "value": "read/write ok",
                "explanation": "File cache under storage/framework/cache. No Redis or Memcached on the hosting."
            },
            {
                "key": "session.driver",
                "label": "Session driver",
                "status": "info",
                "value": "file (/home/mbankole3/web/mbankole3-2.shared-fi01-001.hosting.wayhost.cloud/storage/framework/sessions)",
                "explanation": "Garbage collection runs on a 2/100 lottery inside requests, since no cron can do it."
            },
            {
                "key": "mail.driver",
                "label": "Mail driver",
                "status": "warn",
                "value": "log -> channel mail",
                "explanation": "Mails are written to storage/logs, not delivered. The hosting provides no SMTP relay; use an external SMTP provider for real mail."
            },
            {
                "key": "log.channel",
                "label": "Log channel",
                "status": "info",
                "value": "daily -> /home/mbankole3/web/mbankole3-2.shared-fi01-001.hosting.wayhost.cloud/storage/logs",
                "explanation": "Daily files laravel-YYYY-MM-DD.log, a predictable name (see \"Exposure\")."
            }
        ],
        "Exposure (HTTP self-probes)": [
            {
                "key": "exposure.base",
                "label": "Probing",
                "status": "info",
                "value": "https://mbankole3-2.shared-fi01-001.hosting.wayhost.cloud",
                "explanation": "Each line below is a real HTTP request to this address, without cookies."
            },
            {
                "key": "exposure.user_ini",
                "label": "GET /.user.ini",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (the PHP overrides of this site)."
            },
            {
                "key": "exposure.env",
                "label": "GET /.env",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (credentials (DB password, APP_KEY, OPS_TOKEN))."
            },
            {
                "key": "exposure.log",
                "label": "GET /storage/logs/laravel-2026-11-06.log",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (the application log (stack traces, emails, SQL))."
            },
            {
                "key": "exposure.mail_log",
                "label": "GET /storage/logs/mail-2026-11-06.log",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (logged mails (customer names, emails, order contents))."
            },
            {
                "key": "exposure.composer_lock",
                "label": "GET /composer.lock",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (exact dependency versions, i.e. which advisories apply)."
            },
            {
                "key": "exposure.installed_json",
                "label": "GET /vendor/composer/installed.json",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (exact dependency versions)."
            },
            {
                "key": "exposure.composer_json",
                "label": "GET /composer.json",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (project metadata)."
            },
            {
                "key": "exposure.artisan",
                "label": "GET /artisan",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (PHP source served as text (no .php extension, so it is not executed))."
            },
            {
                "key": "exposure.storage_dir",
                "label": "GET /storage/",
                "status": "ok",
                "value": "403",
                "explanation": "Not served (a directory listing of storage/)."
            },
            {
                "key": "exposure.invoice",
                "label": "GET /storage/app/invoices/LS-2026-000001.html",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (a customer invoice (name, address, items): personal data)."
            },
            {
                "key": "exposure.cache_file",
                "label": "GET /storage/framework/cache/data/a9/59/a959f47ba815ca3976f7d8506b3dc525d5326335",
                "status": "ok",
                "value": "404",
                "explanation": "Not served (a file-cache entry (path is sha1 of the key, so predictable))."
            },
            {
                "key": "exposure.php.vendor",
                "label": "GET /vendor/autoload.php",
                "status": "ok",
                "value": "404",
                "explanation": "Not executable from the web."
            },
            {
                "key": "exposure.php.config",
                "label": "GET /config/database.php",
                "status": "ok",
                "value": "404",
                "explanation": "Not executable from the web."
            },
            {
                "key": "exposure.php.public_index",
                "label": "GET /public/index.php",
                "status": "ok",
                "value": "404",
                "explanation": "Not executable from the web."
            },
            {
                "key": "served.css",
                "label": "GET /css/app.css",
                "status": "ok",
                "value": "200, 9536 bytes",
                "explanation": "Served: the stylesheet (ASSET_URL)."
            },
            {
                "key": "served.upload",
                "label": "GET /storage/products/tasting-notebook-w6rxad.png",
                "status": "ok",
                "value": "200, 3203 bytes",
                "explanation": "Served: an uploaded product image (public disk + storage link)."
            },
            {
                "key": "limits.body.921600",
                "label": "POST 900 KB to /_health/echo",
                "status": "ok",
                "value": "200, PHP received 921600 bytes",
                "explanation": "Request bodies of this size reach the app."
            },
            {
                "key": "limits.body.1572864",
                "label": "POST 1536 KB to /_health/echo",
                "status": "ok",
                "value": "200, PHP received 1572864 bytes",
                "explanation": "Request bodies of this size reach the app."
            },
            {
                "key": "limits.body.3145728",
                "label": "POST 3072 KB to /_health/echo",
                "status": "ok",
                "value": "200, PHP received 3145728 bytes",
                "explanation": "Request bodies of this size reach the app."
            }
        ]
    }
}