Enterprise edition
Compliance — Deep Reference
At a glance
Section titled “At a glance”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.
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 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.
| Tier | Compliance surface |
|---|---|
| Core | In-process byte-stream and grammar checks; no external sidecar delegation. |
| Pro | In-process EN 16931 / Factur-X / ZUGFeRD validation; no external sidecar. |
| Enterprise | External 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.
Public API surface
Section titled “Public API surface”composer require nextpdf/enterprise:^3| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
ComplianceGateway::__construct | list<ExternalValidator> $validators, LoggerInterface $logger, bool $optional = false | Indexes validators by tool name | — | — | Optional mode degrades the availability check to warning-only |
ComplianceGateway::validate | string $pdfContent, ComplianceProfile $profile, array $options = [] | Resolves the validator by ComplianceProfile::toolName(), checks availability, delegates | ?ExternalValidationResult | ComplianceSidecarUnavailableException; InvalidArgumentException (no validator registered for the tool) | Returns null only in optional mode with the sidecar down |
ComplianceGateway::validateAllProfiles | string $pdfContent, string $toolName | Validates every profile mapped to the tool | list<ExternalValidationResult> | Same as validate() | Skips null (optional-mode) results |
ComplianceGateway::healthCheck | — | Probes every registered sidecar health endpoint | array<string, bool> | — | Reports reachability; validates no document |
ComplianceGateway::buildComplianceMatrix (static) | list<ExternalValidationResult> $results, string $commitSha | Reduces results to a schema-versioned matrix | array<string, mixed> | — | Schema version 1.0; records tool output, asserts nothing |
ComplianceProfile (enum) | 15 string-backed cases | Maps each profile to a standard label and a tool | — | — | standardReference(): string, toolName(): string |
ExternalValidator (interface) | — | Sidecar bridge contract over PSR-18 | — | validate() throws ComplianceSidecarUnavailableException on transport failure | getToolName(), isAvailable(), validate() |
VeraPdfValidator::validate | Interface signature | Multipart POST to the veraPDF REST sidecar; JSON report parsing | ExternalValidationResult | ComplianceSidecarUnavailableException; InvalidArgumentException (unsupported profile) | PDF/A, PDF/UA, Arlington; parses JSON only, never XML |
DssValidator::validate | Interface signature | Base64 JSON POST to the EU DSS REST sidecar | ExternalValidationResult | ComplianceSidecarUnavailableException; InvalidArgumentException (unsupported profile) | PAdES B-B through B-LTA; constructor rejects timeouts below one second |
ZugferdExternalValidator::validate | Interface signature | Multipart POST to the combined Mustang/KoSIT sidecar | ExternalValidationResult | ComplianceSidecarUnavailableException (also on an open circuit breaker); InvalidArgumentException (unsupported profile) | ZUGFeRD 2.4, Factur-X 1.08, EN 16931; optional injected circuit breaker |
KoSitValidator::validate | Interface signature | Raw XML POST to a standalone KoSIT daemon | ExternalValidationResult | ComplianceSidecarUnavailableException; InvalidArgumentException (unsupported profile) | EN 16931 only; parses the Schematron SVRL report fail-closed |
ExternalValidationResult | Readonly value object | Normalized tool verdict | — | — | passes(), fails(), nonConformanceCount(), toComplianceMatrix() |
NonConformance | Readonly value object | Single finding with rule id, clause, severity, location | — | — | toArray() |
ComplianceSidecarUnavailableException | string $toolName, string $endpoint, int $code = 0, ?Throwable $previous = null | Fail-closed sidecar unavailability signal | — | — | Public readonly toolName and endpoint |
AiReadyCertifier::certify | string $pdfBytes | Evaluates three readiness criteria; stamps XMP provenance | array{0: AiReadyCertification, 1: string} | InvalidArgumentException (stamping requires a classic cross-reference table) | Second element equals the input when the level is not_certified |
AiReadyCertification | Readonly value object | Readiness assessment with level, criteria count, issues, source hash | — | — | Internal readiness label, not a standards certification |
XRechnungTestSuiteRunner::__construct | string $suitePath, ExternalValidator $validator, bool $useCuratedNegativeFallback = true | Resolves the extracted suite directory | — | InvalidArgumentException (directory does not exist) | Targets the official KoSIT XRechnung test suite |
XRechnungTestSuiteRunner::run | bool $stopOnFirstFailure = false | Validates each suite instance through the bridge | XRechnungTestSuiteResult | XRechnungTestSuiteException (validator unavailable; no XML files) | Also isAvailable(), getSuitePath(), discoverTestFiles() |
XRechnungTestSuiteResult | Readonly value object | Aggregated suite outcome | — | — | allPassed(), totalCount(), getFailures(), getErrors(), toSummary() |
XRechnungTestCaseResult | Readonly value object | Per-case outcome | — | — | passed(), hasError(), getFilename() |
XRechnungTestSuiteException | Static constructors | Suite runtime failure signal | self | — | validatorUnavailable(), 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;}Behavior contract
Section titled “Behavior contract”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 theNEXTPDF_COMPLIANCE_OPTIONALenvironment variable) degrades an unavailable sidecar to a logged warning and anullreturn. Callers must treatnullas “not checked”. Optional mode covers the pre-flight availability probe only; a transport failure during the validation call itself raisesComplianceSidecarUnavailableExceptionin both modes. - Unknown profile. A profile with no registered validator raises
InvalidArgumentException; it never silently passes. - Pass semantics.
ExternalValidationResult::passes()requiresconformantto 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 cases | Standard reference | Tool |
|---|---|---|
pdfa-1b, pdfa-2b, pdfa-3b, pdfa-4, pdfa-4f | ISO 19005-1/-2/-3/-4 (Level B; Level F for 4f) | veraPDF |
pdfua-1, pdfua-2 | ISO 14289-1:2014, ISO 14289-2:2024 | veraPDF |
pdf20-arlington | ISO 32000-2:2020 (Arlington model) | veraPDF |
pades-b-b, pades-b-t, pades-b-lt, pades-b-lta | ETSI EN 319 142-1 B-B through B-LTA | EU DSS |
zugferd-2.4, factur-x-1.08, en-16931 | ZUGFeRD 2.4 / Factur-X 1.08 / EN 16931-1:2017 | Mustang/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.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- Sidecar timeout or transport error surfaces as
ComplianceSidecarUnavailableExceptionfrom 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 thanTOTAL_PASSEDfails withDSS-SIG-INVALID. A signature level below the expected baseline fails withDSS-LEVEL-MISMATCH. DssValidatorpublishes its per-request timeout budget on every request via theX-NextPDF-Timeout-Secondsheader; the integrator’s PSR-18 client must honor it so a stalled sidecar cannot block the calling thread unbounded.ZugferdExternalValidatoroptionally routes sidecar calls through an injected circuit breaker; an open breaker maps toComplianceSidecarUnavailableException(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.AiReadyCertifierstamping fails closed withInvalidArgumentExceptionwhen 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; withuseCuratedNegativeFallbackenabled it substitutes a curated negative corpus when the suite ships no invalid instances.
FIPS-mode behavior
Section titled “FIPS-mode behavior”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.
Conformance
Section titled “Conformance”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.
| Behavior | Reference |
|---|---|
| Conforming-processor obligation; conformance determined against the standard | ISO 19005-4:2020 §5.2 |
| PDF/A-4 file requirements vs. producer self-assertion | ISO 19005-4:2020 §6.6.4 |
| PDF/UA-2 conformity is a property of the file | ISO 14289-2:2024 §6 |
| PAdES baseline signature levels | ETSI 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.
Development notes
Section titled “Development notes”- 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.
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.
See also
Section titled “See also”- Compliance capability overview
- Validation — Deep Reference
- Evidence — Deep Reference
- Pro Compliance — in-process e-invoice (distinct surface)
- Core Conformance