Audit: deterministic compliance-evidence export
At a glance
Section titled “At a glance”The Audit module turns the engine’s conformance-claims dataset into a deterministic, PII-sanitised evidence bundle suitable for a compliance auditor. It sorts every collection for byte-stable output, validates the bundle against a schema, and can project the bundle to a stable version.
Stability: experimental. The exporter itself is deterministic and well-tested, but the upstream
claims.jsondataset it consumes is a work-in-progress conformance matrix (its own_statusreflects that). Until the evidence pipeline reaches GA, treat this module’s output as engineering evidence, not a certified attestation.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”AuditExporter is the single entry point. buildBundle() takes a decoded
claims dataset plus an AuditExportContext and returns an
AuditExportBundle. buildBundleFromFile() is the file-loading convenience.
encode() serializes the bundle to JSON. The build is pure-functional. With a
fixed AuditExportContext::generatedAt (for example via SOURCE_DATE_EPOCH)
and a stable claims dataset, the output is byte-identical across runs because
claims[] are sorted by (standard, clause_id), evidence[] by ref_id,
and clause_hashes[] by clause_id. That is why the reproducibility profile
is bitwise.
The bundle is a typed tree: AuditExportClaim (a conformance claim),
AuditExportEvidence (the supporting evidence record),
AuditExportClauseHash (the clause-content digest), and AuditExportContext
(the generation context). Each carries a toArray() for serialization.
The module is privacy-by-default. The constructor installs a
DefaultPiiSanitiser (a PiiSanitiser implementation) that scrubs evidence
metadata strings before serialization. A different sanitiser can be injected.
The exporter validates clause hashes, evidence digests, and RAG citation
anchors against a strict 64-character lowercase-hex SHA-256 pattern, and emits
a structured warning (rather than a silent drop) when a clause is missing a
valid hash or evaluator metadata. validateAgainstSchema() checks the built
bundle. projectToV1() produces a stable v1 projection. The exporter design
is aligned with OWASP ASVS V8.3, NIST CSF 2.0 PR.PT-1, NIST SP 800-53 r5
AU-2/AU-3, and ISO/IEC 27001 A.12.4 — these are design alignments documented
in the source, not chunk-pinned normative claims.
API surface
Section titled “API surface”| Class | Key members | Role |
|---|---|---|
AuditExporter | buildBundle(), buildBundleFromFile(), encode(), validateAgainstSchema(), projectToV1() | Deterministic bundle builder/validator |
AuditExportBundle | toArray() | The assembled evidence bundle |
AuditExportClaim | toArray() | One conformance claim record |
AuditExportEvidence | toArray() | One supporting evidence record |
AuditExportClauseHash | toArray() | A clause-content digest record |
AuditExportContext | GENERATOR_VERSION | Generation context (pin the timestamp for determinism) |
PiiSanitiser (interface) | sanitise(string): string | Pluggable evidence-metadata scrubber (@since 5.2.0) |
DefaultPiiSanitiser | sanitise() | Privacy-by-default sanitiser (@since 5.2.0) |
Run composer docs:generate-api-php -- --module=Audit for the full PHPDoc
table.
Code sample — Quick start
Section titled “Code sample — Quick start”Build and encode a bundle from a claims file.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Audit\AuditExportContext;use NextPDF\Audit\AuditExporter;
$exporter = new AuditExporter();
$bundle = $exporter->buildBundleFromFile( '/srv/nextpdf/claims.json', new AuditExportContext(/* pin generatedAt for determinism */),);
file_put_contents('/srv/out/audit-bundle.json', $exporter->encode($bundle));Code sample — Production
Section titled “Code sample — Production”Validate the bundle and treat schema or hash warnings as a hard gate.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Audit\AuditExportContext;use NextPDF\Audit\AuditExporter;use Psr\Log\LoggerInterface;
final readonly class EvidenceGate{ public function __construct( private AuditExporter $exporter, private LoggerInterface $logger, ) {}
/** @param array<string, mixed> $claims Decoded claims dataset. */ public function export(array $claims, AuditExportContext $context): string { $bundle = $this->exporter->buildBundle($claims, $context); $errors = $this->exporter->validateAgainstSchema($bundle->toArray());
if ($errors !== []) { $this->logger->error('Audit bundle failed schema validation.', ['errors' => $errors]);
throw new \RuntimeException('Audit evidence bundle is not schema-valid; refusing to publish.'); }
return $this->exporter->encode($bundle); }}Edge cases & gotchas
Section titled “Edge cases & gotchas”- Determinism requires a pinned
AuditExportContext::generatedAt. Without it the timestamp varies and the output is not byte-stable, defeating thebitwiseprofile. - A clause missing a valid 64-hex
clause_hashor evaluator metadata is skipped with a structured warning, not silently included. Check the warnings; a skipped clause is missing evidence, not a pass. - The default
PiiSanitiserruns unless you inject another. Disabling sanitisation is an explicit choice with privacy consequences — do not do it for a regulated export. validateAgainstSchema()is advisory unless you act on its return. Treat a non-empty result as a publish-blocking error in production.- The bundle reflects the input dataset’s maturity. A WIP claims dataset produces a WIP bundle; the exporter does not upgrade evidence quality.
Performance
Section titled “Performance”The build is linear in the number of claims and evidence records, dominated by
sorting. There is no I/O in buildBundle() (the caller owns file access). The
default reference workload is well inside the 1500 ms wall / 64 MB peak budget.
The reproducibility profile is bitwise given a pinned context and stable
input, by design.
Security notes
Section titled “Security notes”This module handles compliance evidence, which can carry sensitive metadata.
PII sanitisation is on by default (GDPR Art. 32 alignment). Keep it on for any
externally shared bundle. The exporter validates every digest and citation
anchor against a strict SHA-256 pattern, so a malformed or truncated hash is
rejected rather than trusted. Treat the input claims dataset as the trust
root: the exporter faithfully serializes what it is given, so the evidence is
only as trustworthy as that dataset. See the engine threat model in
/modules/core/security/.
Conformance
Section titled “Conformance”This module asserts no PDF-specification normative claim. It exports evidence
about conformance; it does not itself implement a cited PDF clause. Its
design alignments (OWASP ASVS V8.3, NIST CSF 2.0, NIST SP 800-53 r5, ISO/IEC
27001 A.12.4) are documented in the source and are control-framework
alignments, not chunk-pinned PDF citations. The conformance the bundle
reports is produced and validated by the oracle and golden suites described
in /modules/core/conformance/.
See also
Section titled “See also”- Compliance module — produces the claims this module exports.
- Metadata module — the XMP audit-field emitter embeds the bundle in-document.
- Conformance overview
- Engine security model