Enterprise edition
Validation — Deep Reference
At a glance
Section titled “At a glance”The Validation module runs pre-built, read-only structural compliance policies against raw PDF bytes. Compliance::assess() applies exactly one CompliancePolicy and returns a ComplianceReport with severity-partitioned findings and a mandatory legal disclaimer. Policies ship for PDF/A-4 (plus the e and f variants), PAdES baseline structure, an eIDAS structural profile, LTV/DSS health, ZUGFeRD / Factur-X, FDA 21 CFR Part 11, and SEC Rule 17a-4 WORM archival. Every policy is a pure function: bytes in, findings out. Validation never mutates the document and never performs cryptographic verification.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Enterprise (nextpdf/enterprise) and activates with an Enterprise-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.
The Validation/Evidence surface is licensed by the enterprise.compliance.evidence capability. A denied entitlement denies the feature rather than degrading silently.
| Tier | Validation surface |
|---|---|
| Core | In-process byte-stream validators and a grammar cross-check; a zero-finding result is a checked result, not a certificate. |
| Pro | In-process EN 16931 / Factur-X / ZUGFeRD validation at the e-invoice layer; no pre-built PDF/A-4, PAdES, LTV, FDA, or SEC policies. |
| Enterprise | Pre-built structural policies for PDF/A-4, PAdES, LTV, ZUGFeRD, FDA Part 11, and SEC 17a-4 with a unified report (this module). |
The Enterprise Compliance external-sidecar gateway is a separate, distinct module.
Public API surface
Section titled “Public API surface”composer require nextpdf/enterprise:^3| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
Compliance::__construct | ?ClockInterface $clock = null | System clock when no clock is injected | — | — | DI-friendly instance form; the clock stamps validatedAt |
Compliance::run | string $pdfData, CompliancePolicy $policy, array $context = [] | Applies exactly one policy and measures wall-clock duration | ComplianceReport | Propagates custom-policy exceptions; built-in policies collect findings instead of throwing | Instance method |
Compliance::assess (static) | string $pdfData, CompliancePolicy $policy, array $context = [] | Constructs a default instance and delegates to run() | ComplianceReport | Same as run() | Zero-configuration fast path |
Policies::pdfA4 / ::pdfA4e / ::pdfA4f (static) | — | PDF/A-4 structural policy per ISO 19005-4:2020 | CompliancePolicy | — | e allows 3D/rich-media annotations; f adds embedded-file relationship checks |
Policies::padesBaseline (static) | — | PAdES B-B structural checks | CompliancePolicy | — | Structure only; no cryptographic verification |
Policies::eidasQualified (static) | — | PAdES structural checks under an eIDAS-labeled profile | CompliancePolicy | — | Qualification depends on the TSP and the qualified certificate |
Policies::ltvHealth (static) | — | DSS structural health check | CompliancePolicy | — | DSS presence resolved from the active object graph, fail-closed |
Policies::zugferd (static) | string $profile = 'BASIC' | Normalizes the profile alias and builds the ZUGFeRD validator | CompliancePolicy | \ValueError (unknown profile) | Profiles: MINIMUM, BASIC, BASIC_WL, EN16931, EXTENDED |
Policies::fdaPart11 (static) | — | FDA 21 CFR Part 11 structural policy | CompliancePolicy | — | Seven structural checks, including audit-trail hash-chain integrity |
Policies::sec17a4 / ::sec17a4Compatible / ::sec17a4Structural / ::sec17a4PreSign (static) | — | SEC 17a-4 WORM policy at the named strictness | CompliancePolicy | — | Strictness maps to WormComplianceLevel |
CompliancePolicy (interface) | — | Strategy contract for one standard | — | — | getName(), getIdentifier(), getStandardReference(), validate(); customer-implementable |
ComplianceReport | Readonly value object | Findings partitioned by severity at construction | — | — | passes(), fails(), totalFindings(), getDisclaimer(); public findings, errors, warnings, infos, policyName, policyId, standard, validatedAt, durationMs |
ComplianceFinding | Severity $severity, string $ruleId, string $message, string $clause = '', string $suggestion = '' | One rule result with clause reference and remediation hint | — | — | Static error() / warning() / info(); isError() |
Severity (enum) | 3 string-backed cases | Error, Warning, Info | — | — | Only Error fails a report |
WormComplianceLevel (enum) | 4 string-backed cases | Full, Compatible, Structural, PreSign | — | — | requiresSignature(), requiresDocMdp(), requiresLtv(), maxDocMdpLevel() |
PdfAPolicy, PadesValidator, LtvHealthCheck, ZugferdValidator, Sec17a4WormPolicy, Fda\FdaPart11Policy | Per-class constructors | Implement CompliancePolicy for one standard each | list<ComplianceFinding> from validate() | — | Obtain via Policies; Sec17a4WormPolicy::getLevel() exposes the configured strictness |
Fda\FdaSigningIntent (enum) | 6 string-backed cases | Authoring, Review, Approval, Certification, Verification, Rejection | — | — | toPdfReasonString() yields the canonical /Reason string |
Fda\FdaAuditEvent::__construct | DateTimeImmutable $timestamp, string $actor, FdaSigningIntent $action, string $documentHash, string $certificateSerial, string $previousEventHash = '' | Computes the SHA-256 chain hash at construction | — | InvalidArgumentException (timestamp not UTC) | Public eventHash; toXmpRdf() serializes one XMP list item |
Fda\FdaAuditTrail::addEvent | FdaAuditEvent $event | Appends the event when its chain link matches the trail tail | self | InvalidArgumentException (hash chain broken) | Also createEvent(), verifyChain(), getLastEventHash(), getEvents(), embedInMetadata() |
Fda\FdaSignatureEnforcer::configureSeedValue | FdaSigningIntent $intent, string $tsaUrl | Builds an FDA-constrained signature seed-value configuration | SeedValueConfig | — | Requires the FDA reason set, a timestamp, and SHA-256 or stronger digests |
Fda\FdaSignatureEnforcer::applyTo | SequentialSigner $signer, SigningStrategy $strategy, string $signerName, FdaSigningIntent $intent, string $tsaUrl, string $fieldName = '', ?string $reason = null | Adds an FDA-constrained signer to a Pro SequentialSigner | SequentialSigner | — | Serializes the constraints into the produced signature field |
namespace NextPDF\Enterprise\Validation;
final readonly class Compliance{ public function __construct(?ClockInterface $clock = null);
/** @param array<string, mixed> $context */ public function run(string $pdfData, CompliancePolicy $policy, array $context = []): ComplianceReport;
/** @param array<string, mixed> $context */ public static function assess(string $pdfData, CompliancePolicy $policy, array $context = []): ComplianceReport;}final class Policies{ public static function pdfA4(): CompliancePolicy; // also pdfA4e(), pdfA4f() public static function padesBaseline(): CompliancePolicy; public static function eidasQualified(): CompliancePolicy; public static function ltvHealth(): CompliancePolicy; public static function zugferd(string $profile = 'BASIC'): CompliancePolicy; public static function fdaPart11(): CompliancePolicy; public static function sec17a4(): CompliancePolicy; // also sec17a4Compatible(), sec17a4Structural(), sec17a4PreSign()}interface CompliancePolicy{ public function getName(): string;
public function getIdentifier(): string;
public function getStandardReference(): string;
/** * @param array<string, mixed> $context * @return list<ComplianceFinding> */ public function validate(string $pdfData, array $context = []): array;}
final readonly class ComplianceReport{ public const string LEGAL_DISCLAIMER;
public function passes(): bool;
public function fails(): bool;
public function totalFindings(): int;
public function getDisclaimer(): string;}Behavior contract
Section titled “Behavior contract”Compliance::assess() (static) and Compliance::run() (instance, with an injectable Psr\Clock\ClockInterface) apply exactly one policy and return a ComplianceReport. Externally observable rules:
- Pure read-only. Every
CompliancePolicy::validate()is a pure function: bytes in, findings out. A policy never mutates PDF bytes. This architectural invariant keeps validation distinct from auto-fix and from the Evidence module. - Severity gate.
ComplianceReport::passes()is true only whenerrors === []. Warnings and infos never fail a report.fails()is the complement. - Mandatory disclaimer.
ComplianceReport::getDisclaimer()returns the constant legal-disclaimer text. Surfacing it in user-facing output is required by the contract. - Report provenance. The report carries the policy name, identifier, and standard reference from the policy, the validation timestamp from the injected or system clock, and the measured duration in milliseconds.
- Collect, do not abort. Built-in policies run all applicable checks and collect every finding rather than stopping at the first error.
- Catalog-reachable DSS only.
LtvHealthCheckresolves DSS presence from the active object graph: active trailer, then/Rootcatalog, then/DSSand its sub-keys. Marker bytes planted in comments, strings, orphan objects, or superseded revisions do not count. Unparseable input is treated as no DSS, so the check fails closed. The check is structural; it does not cryptographically verify embedded OCSP/CRL data. - Structural signature checks.
Policies::padesBaseline()andPolicies::eidasQualified()validate PAdES structure at the PDF level only. Qualification under eIDAS depends on the TSP and the qualified certificate, which are outside this module. - Regulated-industry policies are structural.
FdaPart11Policychecks signature presence,/Reasonintent,/Msigning time,/Nameidentity, JavaScript absence, the FDA audit-trail namespace, and hash-chain integrity.Sec17a4WormPolicychecks up to 13 WORM rules;WormComplianceLevelselects the strictness.Fulldemands DocMDP level 1,Compatibleaccepts level 2, andStructural/PreSignskip signature, DocMDP, and DSS rules. Neither policy establishes legal compliance. - ZUGFeRD context.
Policies::zugferd()checks PDF-level requirements always. It validates invoice XML only when the caller passes['xml' => $xmlData]in$context; otherwise it emits the info findingzugferd-xml-skipped. - Tamper-evident audit trail.
FdaAuditTrailis an append-only SHA-256 hash chain.addEvent()rejects a broken link,verifyChain()re-derives every hash, andembedInMetadata()writes the trail into XMP underhttp://ns.nextpdf.dev/fda/1.0/with a PDF/A extension schema.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- A non-PDF or empty input yields error findings rather than an exception in the built-in policies. Always check
passes()and surface the disclaimer. Policies::zugferd()normalizes profile aliases (BASIC_WL,EN16931,EN_16931). An unknown profile raises\ValueErrorat factory time, before any validation runs.- A DSS with CRLs but no OCSP responses satisfies the revocation-material check; the finding notes the acceptable alternative. Neither present is an error.
- A missing
/VRIdictionary or/Certsarray produces warnings, not errors; the report can still pass. FdaAuditEventrejects any non-UTC timestamp withInvalidArgumentExceptionat construction.FdaAuditTrail::verifyChain()returns false on any tampered or reordered event; it never throws.- Custom
CompliancePolicyimplementations may throw fromvalidate();Compliance::run()does not catch, so such exceptions propagate to the caller.
FIPS-mode behavior
Section titled “FIPS-mode behavior”This module performs no signing, no cryptographic verification, and no key custody. FIPS-mode algorithm policy is governed by the Security and Signature modules. FdaSignatureEnforcer seed values constrain FDA-bound signature fields to SHA-256, SHA-384, or SHA-512 digest methods.
Conformance
Section titled “Conformance”These policies check structural attributes against the named standards. The conformance verdict for ISO/ETSI profiles remains a property of the final file plus an external validator.
| Behavior | Reference |
|---|---|
| Conformance determined against the standard, not the producer | ISO 19005-4:2020 §5.2 |
| Digital signature dictionary / DSS for long-term validation | ISO 32000-2:2020 §12.8 |
DSS is a dictionary held by the DSS key of the document catalog | ISO 32000-2:2020 §12.8.4.3 |
| PAdES baseline signature levels | ETSI EN 319 142-1 §5.4.3 |
| EN 16931 profile semantic model (supporting reference) | Factur-X 1.08 (EN 16931) |
The FDA 21 CFR Part 11 and SEC 17a-4 policies check structural attributes only. The clause strings inside FDA findings (for example §11.50, §11.10(e)) are product-emitted rule references.
Development notes
Section titled “Development notes”- Validation runs in process and local with no network I/O. A policy cannot alter the input.
- Treat PDF bytes from untrusted sources as hostile. The built-in policies are total over arbitrary bytes and fail closed where structure cannot be resolved.
- Surface
ComplianceReport::getDisclaimer()in every user-facing rendering of a report. - Reports and findings may carry personal data from signed documents and audit-trail metadata (signer names, certificate serials). The operator owns retention and minimization controls.
- Custom policies implement
CompliancePolicy; keepgetIdentifier()unique across all policies for serialization and caching. - This module concerns cryptographic functionality; treat it as security-sensitive in your own review.
- Internal mechanism detail stays in the source repository’s internal documentation and is out of scope for this manual.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.