Enterprise edition
AST audit trail — Deep Reference
At a glance
Section titled “At a glance”The Enterprise AST module records document mutations and prepares documents for retrieval pipelines.
AstAuditTrailInterfacedefines an append-only, per-document audit trail over the Pro ASTMutationLog.AstAuditEntryis an immutable record of one mutation: node identity, mutation kind, page, before/after snapshots, UTC timestamp.InMemoryAstAuditTrailis the per-process reference implementation of the trail contract.AstAwareChunkerwalks the AST depth-first and emits citation-anchoredAstChunkvalues for RAG ingestion.
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 AST audit-trail surface is licensed by the enterprise.compliance.evidence capability. A denied entitlement denies the feature.
| Tier | Provides |
|---|---|
| Core | AST document model (AstDocument, AstNode, NodeId) |
| Pro | AST mutation flow and MutationLog |
| Enterprise | Append-only per-document audit trail; citation-anchored chunker |
The Enterprise surface consumes the Pro mutation log. It does not replace the AST model.
composer require nextpdf/enterprise:^3Public API surface
Section titled “Public API surface”| Symbol | Parameters | Default behavior | Returns | Throws or fails with | Notes |
|---|---|---|---|---|---|
AstAuditTrailInterface::record() | string $documentSourceHash, MutationLog $log | Converts each mutation entry in the log to an AstAuditEntry and appends it | void | Nothing in the reference implementation | Repeated calls with the same hash accumulate entries |
AstAuditTrailInterface::findByDocument() | string $documentSourceHash | Returns the entries recorded for one document, in insertion order | list<AstAuditEntry> | Nothing in the reference implementation | Empty list when no entries match the hash |
AstAuditTrailInterface::count() | none | Counts audit entries | int<0, max> | Nothing in the reference implementation | Total across all documents, not per-document |
InMemoryAstAuditTrail | none | Array-backed trail scoped to the current process | implements AstAuditTrailInterface | Nothing | Not durable; suited to single-request lifecycles |
AstAuditEntry::__construct() | promotes all fields | Immutable audit record | value object | Nothing | final readonly; see signature fence below |
AstAwareChunker::__construct() | int $maxChunkChars = 1500, int $overlapChars = 150 | Validates chunking bounds at construction | instance | InvalidArgumentException on out-of-range configuration | Bounds: 16 <= maxChunkChars <= 1048576; 0 <= overlapChars < maxChunkChars |
AstAwareChunker::chunk() | AstDocument $document | Depth-first walk; headings delimit chunks; leaf text accumulates | list<AstChunk> | Nothing | Empty list for a document without accumulable text |
AstChunk::__construct() | promotes all fields | Citation-anchored chunk record | value object | Nothing | final readonly; see signature fence below |
namespace NextPDF\Enterprise\Ast;
use NextPDF\Pro\Ast\Mutation\MutationLog;
interface AstAuditTrailInterface{ public function record(string $documentSourceHash, MutationLog $log): void;
/** @return list<AstAuditEntry> */ public function findByDocument(string $documentSourceHash): array;
/** @return int<0, max> */ public function count(): int;}final readonly class AstAuditEntry{ public function __construct( public readonly string $documentSourceHash, public readonly string $nodeId, public readonly string $mutationType, public readonly int $pageIndex, public readonly array $before, public readonly array $after, public readonly DateTimeImmutable $occurredAt, ) {}}final class AstAwareChunker{ public function __construct( private readonly int $maxChunkChars = 1500, private readonly int $overlapChars = 150, ) {}
/** @return list<AstChunk> */ public function chunk(AstDocument $document): array {}}final readonly class AstChunk{ public function __construct( public readonly string $text, public readonly string $nodeId, public readonly int $pageIndex, public readonly ?array $bbox, public readonly string $nodeType, public readonly string $documentSourceHash, public readonly int $chunkIndex, ) {}}Behavior contract
Section titled “Behavior contract”Audit trail
Section titled “Audit trail”- Append-only. Implementations must be append-only: a recorded entry cannot be modified or removed through this API. Repeated
record()calls with the same hash accumulate entries. - Conversion.
record()converts each entry of the ProMutationLog(viaMutationLog::all()) into anAstAuditEntryand appends it. All entries produced by onerecord()call share one UTCoccurredAttimestamp. - Per-document isolation.
findByDocument()filters on the exact document source hash and preserves insertion order.count()is the total across all documents. - Snapshots.
beforeandafterare attribute maps keyed bytext_content. Anupdatedmutation fills both sides;insertedleavesbeforeempty;deletedleavesafterempty.mutationTypeis the string value of the ProMutationTypeenum:updated,inserted, ordeleted. - Page derivation.
pageIndexis extracted from the canonical node ID (ast:{hash}:{page}:{seq}). A malformed node ID yieldspageIndex0; the entry is still recorded.
Append-only is a contract of the configured store, not a cryptographic property. Tamper-evidence and non-repudiation come from how the trail is persisted and timestamped (Evidence module), not from this module alone.
Chunker
Section titled “Chunker”- Traversal.
chunk()walks the AST depth-first from the document root. - Text accumulation. Leaf text of type Paragraph, ListItem, TableCell, Code, or Annotation accumulates into the current buffer. Container types (Document, Section, Artifact, FormField, Figure, Table, List, TableRow) are traversed without emitting text.
- Delimiters. A Heading node flushes the current buffer as a chunk and seeds the next buffer with the heading text.
- Splitting. When accumulated text would exceed
maxChunkChars, the chunker fills the remaining space, flushes the chunk, and continues with the lastoverlapCharscharacters plus the overflow. Length accounting is UTF-8 character based. - Citation anchor. Each
AstChunkcarries thenodeId,pageIndex,bbox, andnodeTypeof its first contributing node, plus the document source hash and a sequential 0-basedchunkIndex. - Finalization. A trailing buffer with non-whitespace content is flushed as the final chunk; whitespace-only remainders are discarded, and chunk text is trimmed.
Edge cases & failure modes
Section titled “Edge cases & failure modes”- Recording the same
MutationLogtwice accumulates duplicate entries; idempotency must be enforced upstream. - A fresh, unshared
InMemoryAstAuditTrailis always empty. The integration contract requires one sharedAstAuditTrailInterfaceinstance handed to both the mutation-producing flow and the audit-reading consumer, withrecord()called after each successful write. Until then,findByDocument()returns an empty list andcount()returns 0. - The in-memory trail is per-process and not durable; entries do not survive the request that created them. Production supplies a persistent implementation.
- A node ID that fails canonical parsing does not abort recording; the affected entry falls back to
pageIndex0. AstAwareChunker::__construct()rejects degenerate configuration (overlapChars >= maxChunkChars, ormaxChunkCharsoutside[16, 1048576]) withInvalidArgumentException. This prevents unbounded buffer growth during chunking.AstChunk::$bboxisnullwhen the first contributing node carries no bounding box.- A document with no accumulable text yields an empty chunk list.
- This module performs no cryptographic operations. Hashing, signing, and timestamping for tamper-evidence are handled by the Evidence, Security, and Signature modules; FIPS-mode policy lives there.
Conformance
Section titled “Conformance”| Behavior | Reference |
|---|---|
| Incremental-update / signature-integrity context | ISO 32000-2:2020 §12.8 |
The audit trail is a record-keeping aid. It supports audit-style evidence workflows.
Development notes
Section titled “Development notes”- Supply a durable
AstAuditTrailInterfaceimplementation for cross-request retention. Persist it in a WORM-capable store where compliance requires immutability; the append-only guarantee is only as strong as the backing store. - Mutation snapshots can carry personal data; data residency follows the operator’s store.
- The trail consumes the Pro mutation log as produced; it does not re-derive mutations from document state.
- Chunker defaults (
maxChunkChars1500,overlapChars150) suit typical RAG ingestion; tune within the documented bounds for embedding models with different context budgets. - 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.