Pro edition
Compliance — Deep Reference
At a glance
Section titled “At a glance”The Compliance module bundles three independent surfaces under NextPDF\Pro\Compliance:
- Language-tag reporting — a strict PDF/UA-2
/Langpolicy facade plus a structured, PSR-3-shaped compliance-event reporter. - E-invoice handling — Factur-X 1.08 / ZUGFeRD 2.4 validation against the EN 16931 semantic model, and hybrid PDF/A-3 emission.
- Provenance — embed and extract caller-supplied C2PA manifest stores through an adversarial-hardened JUMBF parser; claim synthesis stays preview-gated.
The module reports what it checks. It does not perform cryptographic signing.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.
No per-feature license flag exists. This is a Pro-edition capability. The experimental C2PA claim builder additionally requires an explicit environment opt-in (see Edge cases & failure modes).
Public API surface
Section titled “Public API surface”composer require nextpdf/pro:^3| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
LangComplianceReporter::warn() / ::error() | string $tag, string $reason, ?string $clauseReference = null | Emits one structured JSON record per language-tag event through the PSR-3 logger | void | JsonException if the record fails JSON encoding | warn = lax-mode reject; error = strict-mode reject |
LangComplianceReporter::reportException() | InvalidBcp47TagException $exception, string $severity = 'error' | Extracts tag and reason from the exception; delegates to warn() or error() | void | As above | Convenience path |
LangComplianceReporter::buildRecord() | string $severity, string $tag, string $reason, ?string $clauseReference = null | Builds the record array without logging | array | Does not throw | For custom sinks such as per-file JSON summaries |
ConformancePolicy::default() | ?LoggerInterface $logger = null | Strict UA-2 policy: malformed or unregistered /Lang tags are rejected | self | Does not throw | The v5.0 default is strict |
ConformancePolicy::fromCore() | CoreConformancePolicy $core, ?LoggerInterface $logger = null | Wraps an existing Core policy as-is; no axes flipped | self | Does not throw | Prefer default() for the strict posture |
ConformancePolicy::withStrictUa2() | bool $enabled | Returns a copy with the strict axis set; disabling emits a PSR-3 notice | self | Does not throw | Deprecated opt-out; removal target 6.0.0 |
ConformancePolicy::isStrictUa2() / ::mode() | — | Reads the underlying Core policy | bool / ConformanceMode | Does not throw | — |
EInvoiceValidator::validate() | string $pdfPath | Full pipeline: PDF/A-3 wrapper check, attachment extraction, profile detection, EN 16931 rules, Schematron | EInvoiceValidationResult | EInvoiceException subclass on I/O failure, malformed PDF structure, or tooling crash | Frozen SPI interface; a well-formed non-e-invoice PDF returns a result, never throws |
EInvoiceXmlValidator::validate() | string $xmlPayload, ValidatorContext $context | Structural pre-flight plus the EN 16931 deep-semantic rule corpus over a CII payload | contract ValidationResult | Does not throw for invalid input; rejection surfaces as a failed result with findings | Concrete cross-tier validator; input gated through XmlGuard |
EInvoiceValidationResult::isValid() | — | True only when wrapper, attachment spec, profile, syntax hold and no FATAL violation exists | bool | Does not throw | Empty violation list alone is not validity |
EInvoiceValidationResult::notAnEInvoice() | — | Deterministic all-null, all-false result | self | Does not throw | Factory for the “not a hybrid invoice” case |
EInvoiceProfile | string-backed enum | Cases MINIMUM, BASIC_WL, BASIC, EN16931, EXTENDED, backed by BT-24 URNs | — | — | isEn16931Conformant() is false for MINIMUM and BASIC_WL |
EInvoiceSyntax | string-backed enum | Cases UN_CEFACT_CII, UBL_INVOICE, UBL_CREDIT_NOTE | — | — | Only CII is isFacturXEligible(); UBL is validator-only |
BusinessRuleViolation | string $ruleId, BusinessRuleSeverity $severity, string $message, ?string $xpath = null, ?string $ramPath = null | Immutable violation DTO | — | — | Rule-id families BR-, BR-CO-, BR-CL-, BR-DEC-, BR-FXEXT- |
BusinessRuleSeverity | string-backed enum | FATAL invalidates the invoice; WARNING flags a quality concern | — | — | Mirrors the EN 16931 Schematron levels |
FacturXEmbedder::embed() | see signature fence | Appends embedded-file stream, filespec, and XMP to a PDF/A source; rewrites the xref | void | EInvoiceException on malformed XML, unreadable source, missing catalog, object-stream or xref-stream source, or output write failure | Source file is left intact |
FacturXEmbedderOptions::default() | — | /AFRelationship /Alternative, filename factur-x.xml, type INVOICE, version 1.0 | self | Does not throw | Defaults satisfy the German mandate and remain accepted in France |
FacturXEmbedderOptions::withRelationship() / ::withFilename() | string | Returns a copy with the override applied | self | InvalidArgumentException outside the acceptance sets | Relationships: Source, Data, Alternative; filenames include zugferd-invoice.xml and xrechnung.xml |
FacturXEmbedderOptions::withDocumentType() | string $documentType | Returns a copy with the XMP document-type override | self | Does not throw | Values are not enumerated defensively |
FacturXContractEmbedder::embed() | string $pdfBytes, string $xmlPayload, EmbedderOptions $options | Byte-in, byte-out adapter over FacturXEmbedder via short-lived temp files | string | EInvoiceException; the XRECHNUNG profile is rejected as Enterprise-only | Cross-tier EmbedderInterface implementation |
C2paManifestEmbedder::embed() | string $pdfBytes, ManifestStore $store | Embeds the store’s byte serialisation at the profile location | string | C2paException on any embed failure | Frozen SPI interface; bytes-only, no I/O |
C2paManifestEmbedder::extract() | string $pdfBytes | Parses an embedded store through the hardened JUMBF parser | ManifestStore|null | C2paException subclass when a store is present but breaches a hardening cap | Null signals absence; absence never throws |
ManifestStore::fromBoxes() / ::empty() | list<JumbfBox> / — | Builds the immutable store value object | self | Does not throw | Box order is load-bearing for round-trip equality |
ManifestStore::toBytes() / ::isEmpty() / ::size() | — | Serialises the root boxes; empty store serialises to an empty string | string / bool / int | Does not throw | — |
JumbfBoxParser::parse() | string $bytes | Parses root-level JUMBF boxes under hard caps | list<JumbfBox> | MalformedJumbfException, JumbfBombException, JumbfCycleDetectedException, JumbfDepthExceededException | Caps: depth 8, 64 MiB per box, 128 MiB total, MAX_CHILDREN_PER_SUPERBOX 4096 |
JumbfBox::superbox() / ::leaf() | string $tbox, … | Builds a validated box; toBytes() round-trips through the parser | self | MalformedJumbfException when the TBox is not exactly 4 bytes | — |
C2paCapabilityStatus::current() / ::summary() | — | Reports the C2PA capability maturity, currently preview-draft | self / string | Does not throw | Machine-checkable preview marker |
Feature::PREVIEW_C2PA_DRAFT->isEnabled() | — | Reads the process environment on every call; only the literal '1' enables | bool | Does not throw | Env var NEXTPDF_FEATURE_PREVIEW_C2PA_DRAFT |
ExperimentalC2paEmbedder::buildManifestStore() | string $sourceBytes, string $producer | Builds a draft-pinned manifest store with one SHA-256 hash-binding claim assertion | ManifestStore | Constructor throws LogicException when the preview flag is off | Preview; wire format pinned to a draft snapshot; no claim signature emitted |
Entry-point signatures, verbatim:
public static function default(?LoggerInterface $logger = null): selfpublic function withStrictUa2(bool $enabled): selfpublic function isStrictUa2(): boolpublic function validate(string $pdfPath): EInvoiceValidationResultpublic function embed( string $sourcePdfPath, string $xml, EInvoiceProfile $profile, string $outputPdfPath, ?FacturXEmbedderOptions $options = null,): voidpublic function embed(string $pdfBytes, ManifestStore $store): stringpublic function extract(string $pdfBytes): ?ManifestStoreBehavior contract
Section titled “Behavior contract”Language-tag reporting. LangComplianceReporter emits one structured JSON record per PDF/UA-2 language-tag event. Each record carries the fixed event discriminator, a severity (warn for a lax-mode reject, error for a strict-mode reject), the offending tag verbatim, a machine-readable reason, the parsed tag components (or null when the tag fails the RFC 5646 shape grammar), an ISO 14289-2 §8.4.4 clause reference, and a UTC timestamp with microseconds. The JSON travels as the PSR-3 message body; downstream sinks parse the message field directly. ConformancePolicy is the Premium facade over the Core conformance policy. Its default applies strict UA-2 language handling and rejects a malformed or unregistered tag reaching /Lang. The opt-out helper withStrictUa2(false) reverts to the legacy lax behavior and logs a PSR-3 notice when the effective value actually changes. NextPDF marks that helper deprecated since v5.0 with removal target 6.0.0. To migrate: audit the corpus for malformed /Lang values with composer pdfua2:audit-lang-tags <pdf-or-dir>, correct them, then drop the opt-out call.
E-invoice handling. EInvoiceValidator is the frozen SPI contract for hybrid-PDF validation: PDF/A-3 wrapper check, /AF attachment extraction, profile detection from the BT-24 specification identifier, the EN 16931 business-rule engine, and a Schematron pass. A well-formed non-Factur-X PDF returns EInvoiceValidationResult::notAnEInvoice() rather than throwing; only I/O failures, malformed PDF structure, or tooling crashes raise an EInvoiceException subclass. EInvoiceXmlValidator is the concrete cross-tier XML validator: it gates input through the Core XmlGuard, runs structural pre-flight and the deep EN 16931 semantic rule corpus, and fails closed — engine errors surface as error findings, never as silent passes. FacturXEmbedder amends a PDF/A source into a hybrid PDF/A-3: it appends an embedded-file stream, a filespec with a configurable /AFRelationship, and a Factur-X XMP extension packet, then rewrites the classical cross-reference table. Both the catalog /AF array and the /Names /EmbeddedFiles name tree reference the attachment, so legacy ZUGFeRD readers resolve it.
Provenance. C2paManifestEmbedder embeds a caller-supplied C2PA manifest store into a PDF byte string, or extracts one. ManifestStore is the immutable value object that crosses the boundary. The seam is bytes-only and vendor-neutral: it does not synthesize claims, ingest URI references, or resolve hash bindings, and it performs no network or filesystem I/O. extract() returns null on a miss and is cheap on PDFs without a store. Every non-null extraction has already passed the JumbfBoxParser hardening caps.
This module reports what it checks. The e-invoice validator is not a tax-authority validator and excludes national extensions (for example Italian SDI, French Chorus Pro, German XRechnung). As EN 16931-1 states, the invoice issuer remains responsible for meeting the rules of relevant legislation.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- A well-formed non-Factur-X PDF returns a “not an e-invoice” result; it does not throw.
- An empty business-rule violation list does not by itself mean the document is valid; wrapper and attachment checks also apply.
FacturXEmbedderfails closed on sources using compressed object streams (/Type /ObjStm) or cross-reference streams (/Type /XRef, hybrid/XRefStm). Re-save such sources with a classical cross-reference table first.- XML payloads are gated through the Core
XmlGuard: DOCTYPE or entity declarations, oversized input, and invalid UTF-8 are rejected with anEInvoiceExceptionon the embed path, or a failed result on the validator path. FacturXContractEmbedderrejects theXRECHNUNGprofile loudly instead of silently downgrading it; XRechnung emission is an Enterprise capability.C2paManifestEmbedder::extract()distinguishes absence (null) from malformation (C2paExceptionsubclass naming the breached invariant: malformed structure, size or count bomb, offset cycle, nesting depth).ExperimentalC2paEmbedderconstruction throws aLogicExceptionunless the preview environment flag equals'1'. Its wire format is pinned to a C2PA draft snapshot and can change without notice; it emits no claim signature. This capability stays preview until the C2PA PDF profile freezes.- The strict UA-2 lax opt-out is deprecated; migrate to the strict default (see Behavior contract).
- This module does not perform cryptographic signing. C2PA claim signing and key custody are out of scope; see the Security module for FIPS-mode signing behavior.
Conformance
Section titled “Conformance”| Behavior | Reference | Status |
|---|---|---|
Natural-language declaration (/Lang) | ISO 14289-2:2024 §8.4.4 | Checked / reported |
| Core invoice semantic model | EN 16931-1:2026 | Checked (issuer remains responsible) |
| Associated files / embedded file streams | ISO 32000-2:2020 §14.13.2 | Emitted (/AF, /EF, /Params) |
| Attachment relationship and container rules | Factur-X 1.08 §3.1, §6.2 | Emitted / checked (default /AFRelationship /Alternative) |
| C2PA manifest store / JUMBF | C2PA 2.1 §11.1 | Embed / extract supported; claim synthesis preview |
This records the specifications the module is built against and what it checks or emits.
Development notes
Section titled “Development notes”- The reporter’s record shape is a stable contract; downstream alerting rules may pin against the fixed event discriminator.
- Disabling strict UA-2 emits a telemetry-visible deprecation notice only when the effective value changes; re-asserting the current value is silent.
- The Factur-X embedder preserves source bytes verbatim and appends new objects; it aims to preserve PDF/A-3 conformance but does not re-validate. Pipe the output through an external PDF/A validator for hard attestation.
- The C2PA seam freezes five invariants: no third-party imports, bytes-only contract, no I/O, null-on-miss extraction, and no claim synthesis in the stable layer.
JumbfBoxParsercaps are public constants; size the inputs you accept against them rather than re-deriving limits.
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.