Skip to content
getnextpdf.com

Enterprise edition

Compliance — Deep Reference

The Compliance module routes a finished PDF to an external validation sidecar and returns one normalized result. ComplianceGateway resolves the responsible sidecar from a ComplianceProfile, enforces a fail-closed availability policy, and wraps every tool verdict in an ExternalValidationResult. Bridges ship for veraPDF (PDF/A, PDF/UA, PDF 2.0 Arlington), EU DSS (PAdES levels), the combined Mustang/KoSIT sidecar (ZUGFeRD, Factur-X, EN 16931), and a standalone KoSIT daemon. The module also provides AiReadyCertifier readiness stamping and a runner for the official KoSIT XRechnung test suite.

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 Compliance/Evidence surface is licensed by the enterprise.compliance.evidence capability. A missing or expired entitlement denies the feature; it does not silently downgrade behavior.

TierCompliance surface
CoreIn-process byte-stream and grammar checks; no external sidecar delegation.
ProIn-process EN 16931 / Factur-X / ZUGFeRD validation; no external sidecar.
EnterpriseExternal validator gateway (this module) with a unified result and a fail-closed policy.

The Pro in-process e-invoice validator and the Enterprise external ZUGFeRD sidecar are distinct surfaces. The external validator gateway ships in the nextpdf/enterprise package only.

Terminal window
composer require nextpdf/enterprise:^3
SymbolParametersDefault behaviorReturnsThrows or fails withNotes
ComplianceGateway::__constructlist<ExternalValidator> $validators, LoggerInterface $logger, bool $optional = falseIndexes validators by tool nameOptional mode degrades the availability check to warning-only
ComplianceGateway::validatestring $pdfContent, ComplianceProfile $profile, array $options = []Resolves the validator by ComplianceProfile::toolName(), checks availability, delegates?ExternalValidationResultComplianceSidecarUnavailableException; InvalidArgumentException (no validator registered for the tool)Returns null only in optional mode with the sidecar down
ComplianceGateway::validateAllProfilesstring $pdfContent, string $toolNameValidates every profile mapped to the toollist<ExternalValidationResult>Same as validate()Skips null (optional-mode) results
ComplianceGateway::healthCheckProbes every registered sidecar health endpointarray<string, bool>Reports reachability; validates no document
ComplianceGateway::buildComplianceMatrix (static)list<ExternalValidationResult> $results, string $commitShaReduces results to a schema-versioned matrixarray<string, mixed>Schema version 1.0; records tool output, asserts nothing
ComplianceProfile (enum)15 string-backed casesMaps each profile to a standard label and a toolstandardReference(): string, toolName(): string
ExternalValidator (interface)Sidecar bridge contract over PSR-18validate() throws ComplianceSidecarUnavailableException on transport failuregetToolName(), isAvailable(), validate()
VeraPdfValidator::validateInterface signatureMultipart POST to the veraPDF REST sidecar; JSON report parsingExternalValidationResultComplianceSidecarUnavailableException; InvalidArgumentException (unsupported profile)PDF/A, PDF/UA, Arlington; parses JSON only, never XML
DssValidator::validateInterface signatureBase64 JSON POST to the EU DSS REST sidecarExternalValidationResultComplianceSidecarUnavailableException; InvalidArgumentException (unsupported profile)PAdES B-B through B-LTA; constructor rejects timeouts below one second
ZugferdExternalValidator::validateInterface signatureMultipart POST to the combined Mustang/KoSIT sidecarExternalValidationResultComplianceSidecarUnavailableException (also on an open circuit breaker); InvalidArgumentException (unsupported profile)ZUGFeRD 2.4, Factur-X 1.08, EN 16931; optional injected circuit breaker
KoSitValidator::validateInterface signatureRaw XML POST to a standalone KoSIT daemonExternalValidationResultComplianceSidecarUnavailableException; InvalidArgumentException (unsupported profile)EN 16931 only; parses the Schematron SVRL report fail-closed
ExternalValidationResultReadonly value objectNormalized tool verdictpasses(), fails(), nonConformanceCount(), toComplianceMatrix()
NonConformanceReadonly value objectSingle finding with rule id, clause, severity, locationtoArray()
ComplianceSidecarUnavailableExceptionstring $toolName, string $endpoint, int $code = 0, ?Throwable $previous = nullFail-closed sidecar unavailability signalPublic readonly toolName and endpoint
AiReadyCertifier::certifystring $pdfBytesEvaluates three readiness criteria; stamps XMP provenancearray{0: AiReadyCertification, 1: string}InvalidArgumentException (stamping requires a classic cross-reference table)Second element equals the input when the level is not_certified
AiReadyCertificationReadonly value objectReadiness assessment with level, criteria count, issues, source hashInternal readiness label, not a standards certification
XRechnungTestSuiteRunner::__constructstring $suitePath, ExternalValidator $validator, bool $useCuratedNegativeFallback = trueResolves the extracted suite directoryInvalidArgumentException (directory does not exist)Targets the official KoSIT XRechnung test suite
XRechnungTestSuiteRunner::runbool $stopOnFirstFailure = falseValidates each suite instance through the bridgeXRechnungTestSuiteResultXRechnungTestSuiteException (validator unavailable; no XML files)Also isAvailable(), getSuitePath(), discoverTestFiles()
XRechnungTestSuiteResultReadonly value objectAggregated suite outcomeallPassed(), totalCount(), getFailures(), getErrors(), toSummary()
XRechnungTestCaseResultReadonly value objectPer-case outcomepassed(), hasError(), getFilename()
XRechnungTestSuiteExceptionStatic constructorsSuite runtime failure signalselfvalidatorUnavailable(), noTestFilesFound(string $suitePath)
namespace NextPDF\Enterprise\Compliance;
final class ComplianceGateway
{
/** @param list<ExternalValidator> $validators */
public function __construct(
array $validators,
private readonly LoggerInterface $logger,
private readonly bool $optional = false,
);
/** @param array<string, mixed> $options */
public function validate(
string $pdfContent,
ComplianceProfile $profile,
array $options = [],
): ?ExternalValidationResult;
/** @return list<ExternalValidationResult> */
public function validateAllProfiles(string $pdfContent, string $toolName): array;
/** @return array<string, bool> */
public function healthCheck(): array;
/**
* @param list<ExternalValidationResult> $results
* @return array<string, mixed>
*/
public static function buildComplianceMatrix(array $results, string $commitSha): array;
}
interface ExternalValidator
{
public function getToolName(): string;
public function isAvailable(): bool;
/** @param array<string, mixed> $options */
public function validate(
string $pdfContent,
ComplianceProfile $profile,
array $options = [],
): ExternalValidationResult;
}
enum ComplianceProfile: string
{
case PdfA1b = 'pdfa-1b';
// PdfA2b, PdfA3b, PdfA4, PdfA4f, PdfUa1, PdfUa2, Pdf20Arlington,
// PadesBasic, PadesTimestamp, PadesLongTerm, PadesArchive,
// Zugferd24, FacturX108, En16931
public function standardReference(): string;
public function toolName(): string;
}
final class AiReadyCertifier
{
/** @return array{0: AiReadyCertification, 1: string} Tuple of [certification, stamped PDF bytes] */
public function certify(string $pdfBytes): array;
}

ComplianceGateway::validate() resolves the registered ExternalValidator whose getToolName() matches ComplianceProfile::toolName(), checks isAvailable(), delegates, and returns a normalized ExternalValidationResult. Externally observable rules:

  • Fail-closed default. When the resolved sidecar is unavailable and optional mode is off, the call raises ComplianceSidecarUnavailableException. The document is not checked; it is never treated as passed.
  • Optional mode. Constructing the gateway with optional: true (operators wire this from the NEXTPDF_COMPLIANCE_OPTIONAL environment variable) degrades an unavailable sidecar to a logged warning and a null return. Callers must treat null as “not checked”. Optional mode covers the pre-flight availability probe only; a transport failure during the validation call itself raises ComplianceSidecarUnavailableException in both modes.
  • Unknown profile. A profile with no registered validator raises InvalidArgumentException; it never silently passes.
  • Pass semantics. ExternalValidationResult::passes() requires conformant to be true and zero non-conformances. Every result carries the profile, tool name and version, assertion count, findings, the SHA-256 of the validated bytes, a UTC timestamp, and the call duration.
  • Matrix is a record, not an assertion. buildComplianceMatrix() is a static reducer producing a schema-versioned structure with tool versions and a commit SHA for traceability. It records tool output; it asserts nothing.
  • Data flow. The full PDF byte stream is transmitted to the configured sidecar over a PSR-18 client. Each validation is logged through PSR-3 with profile, tool, pass/fail, assertion count, and duration.

Profile-to-tool routing, as returned by ComplianceProfile::standardReference() and ::toolName():

Profile casesStandard referenceTool
pdfa-1b, pdfa-2b, pdfa-3b, pdfa-4, pdfa-4fISO 19005-1/-2/-3/-4 (Level B; Level F for 4f)veraPDF
pdfua-1, pdfua-2ISO 14289-1:2014, ISO 14289-2:2024veraPDF
pdf20-arlingtonISO 32000-2:2020 (Arlington model)veraPDF
pades-b-b, pades-b-t, pades-b-lt, pades-b-ltaETSI EN 319 142-1 B-B through B-LTAEU DSS
zugferd-2.4, factur-x-1.08, en-16931ZUGFeRD 2.4 / Factur-X 1.08 / EN 16931-1:2017Mustang/KoSIT

AiReadyCertifier::certify() evaluates three criteria: structural signature presence, LTV health, and absence of encryption. Three passed criteria yield level certified; one or two yield partial; zero yields not_certified. At certified or partial, it appends an incremental update carrying an XMP provenance stream and a Catalog override; the original bytes are never mutated. The “certified” level is a NextPDF-internal readiness label, not a standards certification.

VeraPdfValidator parses only JSON sidecar responses (no XML; XXE-clean by construction). KoSitValidator parses the daemon’s XML SVRL report with DOCTYPE declarations rejected and network access disabled, and treats an unparseable report as a failure of the call.

  • Sidecar timeout or transport error surfaces as ComplianceSidecarUnavailableException from the bridge; the fail-closed default applies.
  • A non-200 sidecar response produces a failing result with a tool-specific finding (for example VERAPDF-HTTP-ERROR); it is never a conformance pass.
  • A malformed sidecar JSON or XML body is a validation failure of the call, not a conformance pass.
  • EU DSS results with no signatures fail with DSS-NO-SIGNATURES. An indication other than TOTAL_PASSED fails with DSS-SIG-INVALID. A signature level below the expected baseline fails with DSS-LEVEL-MISMATCH.
  • DssValidator publishes its per-request timeout budget on every request via the X-NextPDF-Timeout-Seconds header; the integrator’s PSR-18 client must honor it so a stalled sidecar cannot block the calling thread unbounded.
  • ZugferdExternalValidator optionally routes sidecar calls through an injected circuit breaker; an open breaker maps to ComplianceSidecarUnavailableException (fail-fast, still fail-closed). The default is a no-op breaker.
  • KoSitValidator::isAvailable() accepts HTTP 200 and 405 from the daemon health probe; the daemon answers GET with 405 while healthy.
  • AiReadyCertifier stamping fails closed with InvalidArgumentException when the original document lacks a classic cross-reference table (for example, cross-reference streams).
  • XRechnungTestSuiteRunner::run() refuses to run when the validator is unavailable or the suite contains no XML files; with useCuratedNegativeFallback enabled it substitutes a curated negative corpus when the suite ships no invalid instances.

This module performs no signing and no key custody. FIPS-mode algorithm policy is governed by the Security and Signature modules. Signature conformance is delegated to EU DSS, which makes its own determination.

The gateway delegates the conformance verdict to an external tool; the design reflects the standards’ own boundary that conformance is determined against the requirements, not asserted by a producer.

BehaviorReference
Conforming-processor obligation; conformance determined against the standardISO 19005-4:2020 §5.2
PDF/A-4 file requirements vs. producer self-assertionISO 19005-4:2020 §6.6.4
PDF/UA-2 conformity is a property of the fileISO 14289-2:2024 §6
PAdES baseline signature levelsETSI EN 319 142-1 §5.4.3

The external tool produces the verdict. Validation results are technical structure-check records for reference, not legal advice.

  • The operator hosts and operates the sidecars, pins their versions, restricts their network reach, validates their TLS, and controls the environment that enables optional mode. Sidecar endpoints are a trust boundary; residency and retention controls for documents, results, and logs are the operator’s responsibility.
  • buildComplianceMatrix() output is designed for CI traceability: pin the commit SHA and archive the matrix beside build artifacts.
  • The XRechnung runner expects the official test suite extracted to a local directory; its constructor message names the public download source.
  • 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.