Skip to content

Audit: deterministic compliance-evidence export

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.json dataset it consumes is a work-in-progress conformance matrix (its own _status reflects that). Until the evidence pipeline reaches GA, treat this module’s output as engineering evidence, not a certified attestation.

Terminal window
composer require nextpdf/core:^3

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.

ClassKey membersRole
AuditExporterbuildBundle(), buildBundleFromFile(), encode(), validateAgainstSchema(), projectToV1()Deterministic bundle builder/validator
AuditExportBundletoArray()The assembled evidence bundle
AuditExportClaimtoArray()One conformance claim record
AuditExportEvidencetoArray()One supporting evidence record
AuditExportClauseHashtoArray()A clause-content digest record
AuditExportContextGENERATOR_VERSIONGeneration context (pin the timestamp for determinism)
PiiSanitiser (interface)sanitise(string): stringPluggable evidence-metadata scrubber (@since 5.2.0)
DefaultPiiSanitisersanitise()Privacy-by-default sanitiser (@since 5.2.0)

Run composer docs:generate-api-php -- --module=Audit for the full PHPDoc table.

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));

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);
}
}
  • Determinism requires a pinned AuditExportContext::generatedAt. Without it the timestamp varies and the output is not byte-stable, defeating the bitwise profile.
  • A clause missing a valid 64-hex clause_hash or 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 PiiSanitiser runs 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.

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.

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/.

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/.