Quality policy
The gates a change has to pass, and why each sits where it does.
The gates that measure written bytes are not here. Conformance (veraPDF, PDF/A, PDF/UA), structure (qpdf), independent verification (pdfsig), certification enforcement (pyHanko), the Arlington grammar, corrupted-input robustness and mutation testing all measure what a writer writes, and this package writes nothing. They run in signet-pdf, where the writer is (0039, 0026).
What is left is what a wrapper can break.
composer check, and it must pass before any commit
pint --test code style, PER-CS
phpstan level max, no baseline
composer deps unused and shadow dependencies
pest the suite, --fail-on-skipped
pest --type-coverage --min=100Type coverage is in that list deliberately. It was a CI-only gate for one release, and the first pull request to trip it was green locally and red in CI on a number no local command reported. A gate CI runs and the documented local command does not is a gate discovered on a pull request.
PHPStan at level max, with no baseline
The baseline was deleted rather than shrunk. The gate is "no errors", not "no new errors", and the only ignores are for Pest's untypeable fluent API, scoped to tests/*.
reportUnmatchedIgnoredErrors is on by default and stays on: an ignore that stops matching is itself an error, which is how the one left behind by a deleted test file was found.
Dead code is refused
PHPStan reports a private method nobody calls and a property only ever written. A local variable assigned and never read is what it misses, so tests/Project/DeadCodeTest.php walks the tree with token_get_all().
It under-reports on purpose, and unused public methods are deliberately not checked: the API exists for consumers whose code is not in this repository.
Structure
tests/Project/ArchTest.php carries the rules that are about shape rather than behaviour:
- only
Adapters\IlluminateProcessRunneropens a process - contracts are interfaces, facades extend Laravel's and are final
- console commands stay in
Commands - every file declares
strict_types=1, checked twice: an arch expectation oversrc/, and a file walk for the files that declare no class - no constant the host platform may not define
- every contract method appears in the README
- docblocks document parameters that exist
tests/Project/SpecTest.php walks every .php, .md and .yml and fails on a cited path that does not resolve, or a cited symbol of this package that no longer exists. It checks paths and symbols, not prose.
tests/Project/DistributionTest.php asks git archive what a release actually contains, so nothing built for testing ships.
The suite
Testbench, grouped by what it covers:
| Directory | Covers |
|---|---|
tests/Adapters | the process runner, the encrypter, the transport |
tests/Io | disks and uploads |
tests/Console | the six artisan commands |
tests/Container | bindings, config, the fake, what the engine can reach |
tests/Project | the structural rules above |
Nothing skips. composer test carries --fail-on-skipped, because every check has to run somewhere and a skip is how one quietly stops.
There is no network group any more. Nothing here reaches an authority: the transport is faked, and whether a real authority interoperates is a question about the request, which signet-pdf builds.
What a patch is expected to carry
A test. tests/Project/ArchTest.php is worth reading before adding a class, since three of its rules constrain where things live.
For a change to behaviour, every surface CONTRIBUTING.md enumerates that describes that behaviour. The list is enumerated rather than summarised because "and any other relevant documentation" is exactly what let three of them go stale at once.