Skip to content
getnextpdf.com

Enterprise edition

Validation — Deep Reference

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.

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.

TierValidation surface
CoreIn-process byte-stream validators and a grammar cross-check; a zero-finding result is a checked result, not a certificate.
ProIn-process EN 16931 / Factur-X / ZUGFeRD validation at the e-invoice layer; no pre-built PDF/A-4, PAdES, LTV, FDA, or SEC policies.
EnterprisePre-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.

Terminal window
composer require nextpdf/enterprise:^3
SymbolParametersDefault behaviorReturnsThrows or fails withNotes
Compliance::__construct?ClockInterface $clock = nullSystem clock when no clock is injectedDI-friendly instance form; the clock stamps validatedAt
Compliance::runstring $pdfData, CompliancePolicy $policy, array $context = []Applies exactly one policy and measures wall-clock durationComplianceReportPropagates custom-policy exceptions; built-in policies collect findings instead of throwingInstance method
Compliance::assess (static)string $pdfData, CompliancePolicy $policy, array $context = []Constructs a default instance and delegates to run()ComplianceReportSame as run()Zero-configuration fast path
Policies::pdfA4 / ::pdfA4e / ::pdfA4f (static)PDF/A-4 structural policy per ISO 19005-4:2020CompliancePolicye allows 3D/rich-media annotations; f adds embedded-file relationship checks
Policies::padesBaseline (static)PAdES B-B structural checksCompliancePolicyStructure only; no cryptographic verification
Policies::eidasQualified (static)PAdES structural checks under an eIDAS-labeled profileCompliancePolicyQualification depends on the TSP and the qualified certificate
Policies::ltvHealth (static)DSS structural health checkCompliancePolicyDSS presence resolved from the active object graph, fail-closed
Policies::zugferd (static)string $profile = 'BASIC'Normalizes the profile alias and builds the ZUGFeRD validatorCompliancePolicy\ValueError (unknown profile)Profiles: MINIMUM, BASIC, BASIC_WL, EN16931, EXTENDED
Policies::fdaPart11 (static)FDA 21 CFR Part 11 structural policyCompliancePolicySeven structural checks, including audit-trail hash-chain integrity
Policies::sec17a4 / ::sec17a4Compatible / ::sec17a4Structural / ::sec17a4PreSign (static)SEC 17a-4 WORM policy at the named strictnessCompliancePolicyStrictness maps to WormComplianceLevel
CompliancePolicy (interface)Strategy contract for one standardgetName(), getIdentifier(), getStandardReference(), validate(); customer-implementable
ComplianceReportReadonly value objectFindings partitioned by severity at constructionpasses(), fails(), totalFindings(), getDisclaimer(); public findings, errors, warnings, infos, policyName, policyId, standard, validatedAt, durationMs
ComplianceFindingSeverity $severity, string $ruleId, string $message, string $clause = '', string $suggestion = ''One rule result with clause reference and remediation hintStatic error() / warning() / info(); isError()
Severity (enum)3 string-backed casesError, Warning, InfoOnly Error fails a report
WormComplianceLevel (enum)4 string-backed casesFull, Compatible, Structural, PreSignrequiresSignature(), requiresDocMdp(), requiresLtv(), maxDocMdpLevel()
PdfAPolicy, PadesValidator, LtvHealthCheck, ZugferdValidator, Sec17a4WormPolicy, Fda\FdaPart11PolicyPer-class constructorsImplement CompliancePolicy for one standard eachlist<ComplianceFinding> from validate()Obtain via Policies; Sec17a4WormPolicy::getLevel() exposes the configured strictness
Fda\FdaSigningIntent (enum)6 string-backed casesAuthoring, Review, Approval, Certification, Verification, RejectiontoPdfReasonString() yields the canonical /Reason string
Fda\FdaAuditEvent::__constructDateTimeImmutable $timestamp, string $actor, FdaSigningIntent $action, string $documentHash, string $certificateSerial, string $previousEventHash = ''Computes the SHA-256 chain hash at constructionInvalidArgumentException (timestamp not UTC)Public eventHash; toXmpRdf() serializes one XMP list item
Fda\FdaAuditTrail::addEventFdaAuditEvent $eventAppends the event when its chain link matches the trail tailselfInvalidArgumentException (hash chain broken)Also createEvent(), verifyChain(), getLastEventHash(), getEvents(), embedInMetadata()
Fda\FdaSignatureEnforcer::configureSeedValueFdaSigningIntent $intent, string $tsaUrlBuilds an FDA-constrained signature seed-value configurationSeedValueConfigRequires the FDA reason set, a timestamp, and SHA-256 or stronger digests
Fda\FdaSignatureEnforcer::applyToSequentialSigner $signer, SigningStrategy $strategy, string $signerName, FdaSigningIntent $intent, string $tsaUrl, string $fieldName = '', ?string $reason = nullAdds an FDA-constrained signer to a Pro SequentialSignerSequentialSignerSerializes 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;
}

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 when errors === []. 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. LtvHealthCheck resolves DSS presence from the active object graph: active trailer, then /Root catalog, then /DSS and 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() and Policies::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. FdaPart11Policy checks signature presence, /Reason intent, /M signing time, /Name identity, JavaScript absence, the FDA audit-trail namespace, and hash-chain integrity. Sec17a4WormPolicy checks up to 13 WORM rules; WormComplianceLevel selects the strictness. Full demands DocMDP level 1, Compatible accepts level 2, and Structural / PreSign skip 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 finding zugferd-xml-skipped.
  • Tamper-evident audit trail. FdaAuditTrail is an append-only SHA-256 hash chain. addEvent() rejects a broken link, verifyChain() re-derives every hash, and embedInMetadata() writes the trail into XMP under http://ns.nextpdf.dev/fda/1.0/ with a PDF/A extension schema.
  • 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 \ValueError at 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 /VRI dictionary or /Certs array produces warnings, not errors; the report can still pass.
  • FdaAuditEvent rejects any non-UTC timestamp with InvalidArgumentException at construction.
  • FdaAuditTrail::verifyChain() returns false on any tampered or reordered event; it never throws.
  • Custom CompliancePolicy implementations may throw from validate(); Compliance::run() does not catch, so such exceptions propagate to the caller.

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.

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.

BehaviorReference
Conformance determined against the standard, not the producerISO 19005-4:2020 §5.2
Digital signature dictionary / DSS for long-term validationISO 32000-2:2020 §12.8
DSS is a dictionary held by the DSS key of the document catalogISO 32000-2:2020 §12.8.4.3
PAdES baseline signature levelsETSI 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.

  • 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; keep getIdentifier() 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.

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.