; PHP settings this application depends on, as a conf.d drop-in. ; ; **This file exists because two domain constants were unreachable.** Round 615 ; measured the estate's upload path end to end and found the limit chain ; INVERTED: `CvUpload::MAX_BYTES` declares 5 MiB and `OnboardingDocument::MAX_BYTES` ; declares 10 MiB, while PHP's COMPILED defaults — no php.ini existed anywhere in ; this repository or its images — refuse at `upload_max_filesize = 2M`. ; ; So a 3 MiB CV, comfortably inside the limit the product declares and squarely ; the size of a phone photo of a printed CV (which the apply page's own copy ; invites: "or a clear photo of it"), was refused with "That file didn't upload ; cleanly — try again, or paste the text instead." Retrying never worked. The ; domain rule that says 5 MiB is acceptable had never once been reached. ; ; `CvUploadTest` asserts that `MAX_BYTES + 1` is refused as "too large", and it ; passes: it builds the value object directly. The test proved the rule works and ; proved nothing about whether the rule can be reached — this session's most ; repeated finding, arriving for the fourth time. ; ; **The rule these numbers obey, enforced by `UploadLimitChainTest`:** every limit ; must be LOOSER than the one inside it, so the innermost — the domain, which is ; the only layer that can produce a sentence a person understands — is what ; actually refuses. Outermost to innermost: ; ; Caddy request_body max_size 20MB = 20,000,000 bytes (docker/Caddyfile) ; post_max_size 16M = 16,777,216 bytes (here) ; upload_max_filesize 12M = 12,582,912 bytes (here) ; OnboardingDocument::MAX_BYTES — = 10,485,760 bytes (the domain) ; CvUpload::MAX_BYTES — = 5,242,880 bytes (the domain) ; ; Anything above the top is refused by the edge with a 413 before a PHP worker is ; occupied. Between the edge and post_max_size, PHP discards the whole body — ; `$_POST` and `$_FILES` both empty — which the application can only answer with a ; bare 422, so that band is kept deliberately narrow rather than removed: it is ; the backstop for a request the edge did not stop. ; ; The numbers are mine. Cham chose the DIRECTION (2026-09-09: raise the runtime to ; match the domain rather than narrow the product to fit an accident) — see ; ADR 0014. upload_max_filesize = 12M post_max_size = 16M ; **Raised with the ceiling above it, and this one is a judgement.** The upload ; path holds the whole file in a PHP string more than once — `(string) ; $file->getStream()`, then the value object, then the storage adapter — so the ; peak is a multiple of the file, not the file. 128M (the compiled default) was ; never sized against a 12 MiB upload; it was sized against nothing, because ; nobody chose it. 256M is headroom for the largest document the domain accepts ; travelling through that path several times over. ; ; It is NOT a measurement. Measuring it needs the real storage adapter under a ; real 10 MiB document, which is a live-vendor path this environment does not ; run. Flagged rather than presented as tuned. memory_limit = 256M ; **Round 805: what PHP itself may say to a visitor — nothing.** The first time ; the production image was served (round 805), a 21 MB form post came back as ; status 200 with PHP's own start-up warning printed at the top of the page — ; "POST Content-Length of 21000000 bytes exceeds the limit of 16777216 bytes" — ; and the application's "too large" page (INV-051) never appeared, because the ; warning had already sent the headers. With no php.ini in the image, PHP runs ; on its compiled defaults, and those display errors and log none. ; ; The application renders every error itself (Yii's ErrorHandler, which turns ; warnings into exceptions); PHP writing into a response can only leak paths ; and break the response it lands in. Logged instead, to stderr, where the ; container's logs are read. display_errors = Off display_startup_errors = Off log_errors = On expose_php = Off ; Round 1029 (ASVS 5.0 V16.2.5): a stack trace names the functions it passed ; through and not the values they were given. The compiled default is Off, so ; an exception logged in production carried names, addresses and anything ; else a function was called with, cut at 15 characters. zend.exception_ignore_args = On ; Stated rather than inherited, because production depends on it: ; config/common/di/db.php reads every DB_* setting from $_ENV, and PHP fills ; $_ENV only when this contains E. php.ini-production's "GPCS" would leave all ; five unset, and production (round 804) would refuse to start. The compiled ; default happens to be EGPCS; a default is not a decision. variables_order = "EGPCS"