Skip to content
getnextpdf.com

Enterprise edition

Evidence — Deep Reference

This page is the deep reference for the NextPDF\Enterprise\Evidence module. The module seals validation findings into an immutable EvidencePackage, exports it as deterministic JSON with a stable SHA-256 digest, persists it through a pluggable store contract, and tracks regressions between runs with ContinuousMonitor. The module consumes findings produced by the Validation and Compliance surfaces; it performs no conformance checks itself. For workflow guidance, read the Evidence capability page first.

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 surface is licensed by the enterprise.compliance.evidence capability; a denied entitlement denies the feature. Core and Pro produce findings and reports; sealing findings into an immutable, deterministic, optionally timestamped package with regression tracking has no Core-tier or Pro-tier equivalent.

Terminal window
composer require nextpdf/enterprise:^3
SymbolParametersDefault behaviorReturnsThrows or fails withNotes
EvidencePortal::__constructEvidenceStoreInterface $store, EvidenceExporter $exporterWires the store and exporterEvidencePortalNothing declaredBoth collaborators are injectable
EvidencePortal::generateEvidencestring $documentHash, list<EvidenceRecord> $records, ?string $tsaTimestamp = nullCounts pass/fail, seals a package with a fresh UUID id and wall-clock generatedAt, persists itEvidencePackageNothing declaredPersists through store(), not persistImmutable()
EvidencePortal::getEvidencestring $documentHashLatest stored package for the hash?EvidencePackageNothing declarednull when none stored
EvidencePortal::getHistorystring $documentHashFull history, newest firstlist<EvidencePackage>Nothing declaredOrdering is supplied by the store
EvidencePortal::exportAsJsonEvidencePackage $packageDelegates to the exporternon-empty-stringJsonExceptionSame bytes as EvidenceExporter::toJson
EvidencePackage::__constructeight named parameters, see fenceImmutable value objectEvidencePackageNothing declaredCounts are not validated against $records
EvidencePackage::allPassednonefailedCount === 0boolNothing declaredtrue for an empty package; gate on totalFindings
EvidencePackage::passRatenonepassedCount / totalFindingsfloatNothing declared0.0 when totalFindings === 0
EvidenceRecord::__constructstring $policyName, bool $passed, string $details, string $validatorVersion, DateTimeImmutable $timestampImmutable single policy-check resultEvidenceRecordNothing declaredAll properties are public readonly
EvidenceExporter::toJsonEvidencePackage $packageFixed-key-order JSON; unescaped slashes and Unicodenon-empty-stringJsonExceptionKey order is load-bearing
EvidenceExporter::exportHashEvidencePackage $packageSHA-256 over the toJson() bytesnon-empty-string (64 hex)JsonExceptionStable per package
EvidenceStoreInterface::storeEvidencePackage $packageAppends; history per document hash is allowedvoidImplementation-definedAppend-only semantics required
EvidenceStoreInterface::persistImmutableEvidencePackage $packageWORM write where the backend supports itvoidImplementation-definedNon-WORM backends behave as store()
EvidenceStoreInterface::findByDocumentHashstring $documentHashMost recent package for the hash?EvidencePackageImplementation-defined
EvidenceStoreInterface::findAllByDocumentHashstring $documentHashAll packages for the hash, newest firstlist<EvidencePackage>Implementation-defined
EvidenceStoreInterface::countnoneTotal number of stored packagesint<0, max>Implementation-defined
InMemoryEvidenceStoreclassArray-backed store for tests and developmentn/an/aNot durable; no WORM semantics
ContinuousMonitor::__constructEvidenceStoreInterface $storeWires the storeContinuousMonitorNothing declared
ContinuousMonitor::checkEvidencePackage $currentEvidence, string $documentHashDiffs failed policy names against the stored latest packageMonitorResultNothing declaredFirst check treats every current failure as new
ContinuousMonitor::isDuestring $documentHash, MonitorSchedule $scheduleDue when no prior evidence, the interval elapsed, or stored evidence is future-datedboolNothing declaredFail-safe on clock skew
MonitorResult::__constructeight named parameters, see fenceImmutable diff resultMonitorResultNothing declaredIncludes both packages and checkedAt
MonitorSchedule::__constructMonitorFrequency $frequency, int $retentionDays = 90, bool $alertOnNewIssues = trueConfiguration value objectMonitorScheduleNothing declaredRetention and alerting are host-enforced
MonitorFrequencystring-backed enumCases Daily, Weekly, Monthlyn/an/aBacking values daily, weekly, monthly
MonitorFrequency::intervalSecondsnoneInterval per case: 86400, 604800, 2592000positive-intNothing declaredMonthly is a fixed 30 days
final class EvidencePortal
{
public function __construct(
private readonly EvidenceStoreInterface $store,
private readonly EvidenceExporter $exporter,
)
public function generateEvidence(string $documentHash, array $records, ?string $tsaTimestamp = null): EvidencePackage
public function getEvidence(string $documentHash): ?EvidencePackage
public function getHistory(string $documentHash): array
public function exportAsJson(EvidencePackage $package): string
}
final readonly class EvidencePackage
{
public function __construct(
public string $packageId,
public string $documentHash,
public array $records,
public int $totalFindings,
public int $passedCount,
public int $failedCount,
public DateTimeImmutable $generatedAt,
public ?string $tsaTimestamp = null,
)
public function allPassed(): bool
public function passRate(): float
}
final readonly class EvidenceRecord
{
public function __construct(
public string $policyName,
public bool $passed,
public string $details,
public string $validatorVersion,
public DateTimeImmutable $timestamp,
)
}
final readonly class EvidenceExporter
{
public function toJson(EvidencePackage $package): string
public function exportHash(EvidencePackage $package): string
}
interface EvidenceStoreInterface
{
public function store(EvidencePackage $package): void;
public function persistImmutable(EvidencePackage $package): void;
public function findByDocumentHash(string $documentHash): ?EvidencePackage;
public function findAllByDocumentHash(string $documentHash): array;
public function count(): int;
}
final class ContinuousMonitor
{
public function __construct(
private readonly EvidenceStoreInterface $store,
)
public function check(EvidencePackage $currentEvidence, string $documentHash): MonitorResult
public function isDue(string $documentHash, MonitorSchedule $schedule): bool
}
final readonly class MonitorSchedule
{
public function __construct(
public MonitorFrequency $frequency,
public int $retentionDays = 90,
public bool $alertOnNewIssues = true,
)
}
enum MonitorFrequency: string
{
case Daily = 'daily';
case Weekly = 'weekly';
case Monthly = 'monthly';
public function intervalSeconds(): int
}

EvidencePortal::generateEvidence(string $documentHash, list<EvidenceRecord> $records, ?string $tsaTimestamp = null): EvidencePackage is the sealing entry point. Externally observable rules:

  1. Assembly. generateEvidence counts passed and failed records and sets totalFindings to their sum. It assigns a fresh version-4 UUID packageId, stamps generatedAt with the wall clock, persists the package through EvidenceStoreInterface::store, and returns it. The record list is embedded in the given order, unmodified.
  2. Immutability. EvidencePackage is final readonly and never mutated after construction; it is suitable for WORM storage. allPassed() is failedCount === 0. passRate() is passedCount / totalFindings, and 0.0 when totalFindings === 0.
  3. Deterministic export. EvidenceExporter::toJson emits the envelope and each record with a fixed, hand-written key order; the record sequence follows the package. Encoding is strict and throws on failure, with slashes and Unicode left unescaped (JSON_UNESCAPED_SLASHES). Timestamps serialize with DateTimeInterface::RFC3339_EXTENDED, the RFC 3339 extended form with fractional seconds. exportHash returns the 64-character SHA-256 hex digest over exactly those bytes. The same package always yields the same digest, on any host, at any time. Re-generating evidence for the same document yields a new packageId and generatedAt, so a new digest: determinism is per package, not per document.
  4. Timestamp is evidence of time, not a verdict. A package may carry an optional caller-supplied RFC 3161 token (base64-encoded). The token binds the package datum to a time value. The module embeds it as an opaque string; it does not fetch, parse, or verify tokens, and it does not vouch for the TSA. Token verification belongs to the Signature and Security modules.
  5. Regression tracking. ContinuousMonitor::check loads the stored latest package for the document hash and diffs unique failed policy names. Issues are categorized as newIssues (failed now, not before), resolvedIssues (failed before, not now), and unchangedIssues (failed in both). hasChanges is true only when new or resolved issues exist; unchanged failures alone report false. On a first check, every current failure is new.
  6. Scheduling. ContinuousMonitor::isDue returns true when no evidence exists for the hash, when the elapsed time since the stored generatedAt reaches the schedule frequency interval, or when the stored evidence is future-dated relative to the polling host. The future-dated case is fail-safe: at worst an extra re-check, never a missed one.
  7. Store contract. EvidenceStoreInterface implementations must support append-only semantics; multiple packages per document hash form the history, newest first. persistImmutable targets WORM-capable backends; non-WORM implementations must behave exactly like store.
  • An empty package reports allPassed() true and passRate() 0.0. Gate on totalFindings > 0 before treating a package as a pass.
  • Direct EvidencePackage construction does not validate the counts against $records. Use the portal, or keep the counts consistent yourself.
  • generateEvidence persists before returning. Run ContinuousMonitor::check with the new package before persisting it; a check after persistence diffs the package against itself and reports no changes.
  • exportHash covers the exact toJson bytes. A digest recomputed by any other serializer, key order, or escaping policy will not match.
  • MonitorFrequency::Monthly is a fixed 30-day window, not a calendar month.
  • MonitorSchedule::$retentionDays and $alertOnNewIssues are configuration carried for host schedulers. The module never deletes evidence and never sends alerts.
  • InMemoryEvidenceStore is for tests and development. Packages are lost at process exit, and its persistImmutable has no WORM semantics.
  • Record details strings are exported verbatim; the exporter does not redact. Keep secrets and regulated personal data out of details. Residency, retention, and access control follow the operator’s store implementation.
  • The tsaTimestamp argument is accepted as an opaque string. A malformed token is embedded unchanged and surfaces only at downstream verification.

This module computes SHA-256 digests and embeds a caller-supplied RFC 3161 token. It performs no signing and no key custody. FIPS-mode behavior is governed by the Security and Signature modules.

ClaimStandardClause
A time-stamp token indicates that a datum existed at a particular point in time.IETF RFC 3161§2
Exported timestamps use the Internet date/time profile of ISO 8601, with fractional seconds.IETF RFC 3339§5.6
Validation material embedded inside a PDF belongs to the Document Security Store; that surface is the Signature module’s, not this one.ISO 32000-2:2020§12.8.4

All clauses are paraphrased; NextPDF does not reproduce normative text. NextPDF makes no certification claim. Evidence capture supports audit workflows; it is not a legal attestation and not an audit certification. A timestamp token is evidence of time only, and this module does not assert that any content is compliant. Validity and conformance remain properties of the final file plus a validator. This reference is not a legal opinion; consult your own compliance and legal advisers.

  • The module source carries @since 2.2.0; this reference documents the surface as shipped in nextpdf/enterprise 3.1.0.
  • Everything runs in process on your host. The module performs no network I/O and never contacts a TSA itself.
  • The exporter’s array-literal key order is load-bearing by design. Reordering it would change exportHash and invalidate previously stored digests; the source forbids it.
  • packageId is a version-4 UUID assembled from \random_bytes(16) output; identifiers are unique but not reproducible.
  • Durable persistence is supplied by the host. WORM enforcement and access control are the operator’s responsibility; the in-memory store is the only bundled implementation.
  • MonitorResult is a final readonly value object; its eight properties are public, including checkedAt, the wall-clock time of the check.

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.