vole_document/field/node.rs
1//! Canonical procedural seed nodes (Phase 11, ADR-0025).
2//!
3//! A [`SeedNode`] describes one **computation**, not one storage slot: a bounded,
4//! versioned materializer over zero or more dependencies, producing a declared
5//! output kind and logical length. Nodes are canonically encoded (little-endian,
6//! length-prefixed, no serde) so that the same computation always hashes to the
7//! same [`NodeId`] regardless of platform or insertion order.
8//!
9//! The node's `content_id` is **not** stored inside the node: it is
10//! `NodeId::of_node(canonical_bytes)`, which is what makes the graph
11//! content-addressed and immutable. Changing a dependency's bytes yields a
12//! different dependency id, hence a different node id; there is no mutation.
13
14use crate::error::{Error, Result};
15use crate::limits::Limits;
16use crate::store::NodeId;
17
18/// Canonical header byte for the node encoding.
19pub const NODE_MAGIC: u8 = 0xB1;
20/// Recommended maximum canonical node size (framing discipline).
21pub const MAX_NODE_BYTES: usize = 64 * 1024;
22/// Maximum dependency fanout of a single node.
23pub const MAX_NODE_DEPS: usize = 256;
24/// The materializer semantics version for v1.
25pub const MATERIALIZER_VERSION: u16 = 1;
26
27/// What a node computes. The numeric values are part of the canonical encoding.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[repr(u8)]
30pub enum NodeKind {
31 /// The whole exact source, materialized from the descriptor blob.
32 DocumentExact = 0x01,
33 /// An exact byte span of the source.
34 SourceSlice = 0x02,
35 /// An exact physical revision span.
36 PdfRevision = 0x03,
37 /// An exact indirect-object byte span (`n g obj … endobj`).
38 PdfObject = 0x04,
39 /// The exact encoded bytes of a stream object's data.
40 PdfStreamEncoded = 0x05,
41 /// The decoded (inflated) bytes of a lone `/FlateDecode` stream.
42 PdfStreamDecoded = 0x06,
43 /// A decoded content stream's operator token span.
44 ContentOperators = 0x07,
45 /// A deterministic text-run projection of content operators.
46 TextRuns = 0x08,
47 /// A page's concatenated decoded content stream.
48 PageContent = 0x09,
49 /// A deterministic structured page preview.
50 PagePreview = 0x0A,
51 /// A physical byte span of one resource object referenced by a page.
52 ResourceRef = 0x0B,
53 /// Concatenation of dependency outputs (exact).
54 Concat = 0x0C,
55 /// A raw exact literal held in the seed store.
56 Literal = 0x0D,
57 /// The whole exact package (ZIP/OCF/OPC) source, materialized through the
58 /// descriptor (`serve_document`). Exact (Phase 12.2).
59 PackageRoot = 0x0E,
60 /// One package member's exact raw compressed/stored span (a `SourceSlice`
61 /// with package provenance). Exact (Phase 12.2).
62 PackageMemberRaw = 0x0F,
63 /// One package member's decoded bytes: raw-DEFLATE inflate (method 8) or the
64 /// stored identity (method 0). Derived, never exact (Phase 12.2).
65 PackageMemberDecoded = 0x10,
66 /// The canonical generic-OPC package graph (content types, parts, package and
67 /// part relationships) derived on demand from the exact package source.
68 /// Derived, never exact (Phase 12.3).
69 PackageOpcModel = 0x11,
70 /// The canonical DOCX discovery model (main part, styles part, stories)
71 /// derived on demand from the canonical OPC model. Derived, never exact
72 /// (Phase 12.4).
73 DocxModel = 0x12,
74 /// One WordprocessingML story parsed into its canonical [`crate::adapter::docx::wml::StoryModel`],
75 /// honoring a declared extraction profile. Derived, never exact (Phase 12.4).
76 DocxStory = 0x13,
77 /// The canonical EPUB (OCF) discovery model: `mimetype` conformance facts,
78 /// container rootfiles, and the Package Document metadata/manifest/spine,
79 /// derived on demand from the exact package source. Derived, never exact
80 /// (Phase 12.5).
81 EpubModel = 0x14,
82 /// One spine item's XHTML content document parsed into its bounded native
83 /// content model (headings/paragraphs/lists/tables/links/resources), honoring
84 /// a declared extraction profile. Derived, never exact (Phase 12.6).
85 EpubContent = 0x15,
86 /// A byte-identical shareable resource (image/font/attachment) held inline and
87 /// addressed by **content identity** (Phase 12.8, ADR-0034). Its canonical
88 /// encoding embeds the exact bytes, so two documents carrying the same resource
89 /// share one node id (and one persisted blob) with no second identity scheme.
90 /// Exact (a resource's bytes are the source bytes).
91 ResourceBlob = 0x16,
92 /// The canonical ODT (ODF) discovery model: `mimetype` conformance facts and the
93 /// parsed `META-INF/manifest.xml` file entries with the main content part resolved
94 /// semantically, derived on demand from the exact package source. Derived, never
95 /// exact (Phase 13.3).
96 OdtModel = 0x17,
97 /// The OpenDocument main part (`content.xml`, `office:text`) parsed into its
98 /// bounded native content model (paragraphs/headings/spans/lists/tables/links/
99 /// bookmarks/notes/resources/tracked changes/sections), honoring a declared
100 /// extraction profile. Derived, never exact (Phase 13.3).
101 OdtContent = 0x18,
102 /// The PDF **revision lineage** (Phase 17): a compact JSON projection of the
103 /// physical incremental revision chain (count, ordered indices, byte spans,
104 /// resolved `startxref`/`/Prev`, and per-revision object/stream membership),
105 /// computed once at ingest from the byte-authoritative scan. Derived, never
106 /// exact. There is one document-level node (the whole lineage) and one
107 /// per-revision node.
108 PdfRevisionLineage = 0x19,
109 /// The canonical XLSX (SpreadsheetML) discovery model (Phase 21.1.1): the
110 /// workbook part (resolved via the `officeDocument` relationship and its
111 /// SpreadsheetML content type), the styles and shared-strings parts, and the
112 /// discovered worksheet parts. Derived on demand from the canonical OPC model.
113 /// Derived, never exact.
114 XlsxModel = 0x1A,
115 /// The parsed `xl/workbook.xml` sheet inventory (name, sheetId, r:id, state,
116 /// document order), parsed from the decoded workbook member. Derived, never
117 /// exact (Phase 21.1.1).
118 XlsxWorkbook = 0x1B,
119 /// One worksheet part (`xl/worksheets/sheetN.xml`) parsed into its bounded cell
120 /// model, honoring a declared extraction profile and resolving shared strings
121 /// from the decoded shared-strings member. Derived, never exact (Phase 21.1.1).
122 XlsxSheet = 0x1C,
123 /// The canonical PPTX (PresentationML) discovery model (Phase 21.2.1): the main
124 /// presentation part (resolved via the `officeDocument` relationship and its
125 /// PresentationML content type), the notes/slide masters, the layouts, themes
126 /// and media parts, and the discovered slide parts. Derived on demand from the
127 /// canonical OPC model. Derived, never exact.
128 PptxModel = 0x1D,
129 /// The parsed `ppt/presentation.xml` slide inventory (slide size and the
130 /// `p:sldIdLst` order), parsed from the decoded presentation member. Derived,
131 /// never exact (Phase 21.2.1).
132 PptxPresentation = 0x1E,
133 /// One slide part (`ppt/slides/slideN.xml`) parsed into its bounded shape model,
134 /// honoring a declared extraction profile. Derived, never exact (Phase 21.2.1).
135 PptxSlide = 0x1F,
136 /// One notes-slide part (`ppt/notesSlides/notesSlideN.xml`) parsed into its
137 /// bounded text model. Derived, never exact (Phase 21.2.1).
138 PptxNotes = 0x20,
139 /// The canonical ODS (ODF spreadsheet) discovery model: `mimetype` conformance
140 /// facts and the parsed `META-INF/manifest.xml` file entries with the main
141 /// content part resolved semantically, derived on demand from the exact package
142 /// source. Derived, never exact (Phase 21.3.1).
143 OdsModel = 0x21,
144 /// The OpenDocument spreadsheet main part (`content.xml`, `office:spreadsheet`)
145 /// parsed into its bounded native model (sheets/rows/cells with typed values,
146 /// stored formulas, cell styles, named expressions, and annotations), honoring a
147 /// declared extraction profile. Derived, never exact (Phase 21.3.1).
148 OdsContent = 0x22,
149 /// The OpenDocument styles part (`styles.xml`) parsed into its bounded cell-style
150 /// and number-format model. Derived, never exact (Phase 21.3.1).
151 OdsStyles = 0x23,
152 /// The canonical ODP (ODF presentation) discovery model: `mimetype` conformance
153 /// facts and the parsed `META-INF/manifest.xml` file entries with the main
154 /// content part and the `Pictures/*` media parts resolved semantically, derived
155 /// on demand from the exact package source. Derived, never exact (Phase 21.4.1).
156 OdpModel = 0x24,
157 /// The OpenDocument presentation main part (`content.xml`, `office:presentation`)
158 /// parsed into its bounded native model (`draw:page` slides in document order,
159 /// shapes/text runs, embedded tables, notes, styles, and image references),
160 /// honoring a declared extraction profile. Derived, never exact (Phase 21.4.1).
161 OdpContent = 0x25,
162 /// The OpenDocument styles part (`styles.xml`) parsed into its bounded style and
163 /// master-page model. Derived, never exact (Phase 21.4.1).
164 OdpStyles = 0x26,
165 /// The canonical, representation-preserving JSON structured-tree model
166 /// (Phase 21.5.1): every token's exact source span, member order, duplicate
167 /// keys, and token spelling. It is derived on demand from the exact source
168 /// (its single dependency is the `DocumentExact` root, keyed by
169 /// `sha256(source)` per ADR-0060). JSON has no package layer. Derived, never
170 /// exact.
171 JsonModel = 0x27,
172 /// The canonical, representation-preserving YAML structured-tree model
173 /// (Phase 21.6.1): every node's exact source span and kind/style, the anchored
174 /// graph (anchors preserved, aliases never expanded), literal tags, scalar
175 /// styles, ordered documents, merge keys, and comment spans. It is derived on
176 /// demand from the exact source (its single dependency is the `DocumentExact`
177 /// root, keyed by `sha256(source)` per ADR-0060). YAML has no package layer.
178 /// Derived, never exact.
179 YamlModel = 0x28,
180 /// The canonical, representation-preserving CSV/TSV tabular model
181 /// (Phase 21.7.1): every record's and field's exact source span, the recorded
182 /// dialect (delimiter/quote/line terminator/BOM), original quoting, and the
183 /// header row. It is derived on demand from the exact source (its single
184 /// dependency is the `DocumentExact` root, keyed by `sha256(source)` per
185 /// ADR-0060). CSV/TSV has no package layer. Derived, never exact.
186 CsvModel = 0x29,
187 /// The canonical, representation-preserving Markdown prose model (Phase
188 /// 21.8.1): every block's and inline span's exact source span, ATX headings and
189 /// their levels, paragraphs, ordered/unordered lists, fenced (language-tagged)
190 /// and indented code blocks, blockquotes, tables, links/images with targets and
191 /// titles, reference definitions, footnotes, and front matter. It is derived on
192 /// demand from the exact source (its single dependency is the `DocumentExact`
193 /// root, keyed by `sha256(source)` per ADR-0060). Markdown has no package layer.
194 /// Derived, never exact.
195 MarkdownModel = 0x2A,
196 /// The canonical, representation-preserving XML structured-tree model
197 /// (Phase 21.9): every element's qualified-name/start-tag/end-tag/full span,
198 /// every attribute's name/quoted-value/inner-value/full span, every text run,
199 /// CDATA section, comment, processing instruction, DOCTYPE, and namespace
200 /// declaration, all in document order. Entity references are surfaced literally
201 /// (never expanded) and no DTD internal subset is processed. It is derived on
202 /// demand from the exact source (its single dependency is the `DocumentExact`
203 /// root, keyed by `sha256(source)` per ADR-0060). XML has no package layer.
204 /// Derived, never exact.
205 XmlModel = 0x2B,
206 /// The canonical, representation-preserving HTML document model (Phase 21.10):
207 /// every element's qualified-name/start-tag/end-tag/full span, every attribute's
208 /// name/quoted-value/inner-value/full span (quoted, single-quoted, unquoted, or
209 /// boolean), every text run, comment, DOCTYPE, and raw `<script>`/`<style>`
210 /// content, all in document order. The parser is bounded and
211 /// error-recovering (implicit tag closing, void elements, stray end tags);
212 /// entity references are surfaced literally (never expanded) and `script`/`style`
213 /// content is captured as raw bytes (never executed). It is derived on demand
214 /// from the exact source (its single dependency is the `DocumentExact` root,
215 /// keyed by `sha256(source)` per ADR-0060). HTML has no package layer. Derived,
216 /// never exact.
217 HtmlModel = 0x2C,
218 /// The canonical, representation-preserving TOML model (Phase 21.11): every
219 /// table (`[a.b]`), array of tables (`[[a]]`), inline table, array, key, value,
220 /// and comment with its exact source span; dotted keys build the tables they
221 /// name; every scalar keeps its **exact spelling** (strings, integers, floats
222 /// incl. `inf`/`nan`, booleans, and date-times). TOML's duplicate-key /
223 /// redefinition rules are enforced. It is derived on demand from the exact
224 /// source (its single dependency is the `DocumentExact` root, keyed by
225 /// `sha256(source)` per ADR-0060). TOML has no package layer. Derived, never
226 /// exact.
227 TomlModel = 0x2D,
228 /// The canonical, per-line JSONL/NDJSON model (Phase 21.12): every non-blank
229 /// record's **exact source line span** and terminator, and its parse by the
230 /// shared JSON parser (so each record's member order, duplicate keys, numeric
231 /// and escape spelling, and exact token spans are preserved). It is derived on
232 /// demand from the exact source (its single dependency is the `DocumentExact`
233 /// root, keyed by `sha256(source)` per ADR-0060). JSONL has no package layer.
234 /// Derived, never exact.
235 JsonlModel = 0x2E,
236 /// The canonical, representation-preserving EML/MIME message model (Phase
237 /// 21.13): every header's exact name/value/full span and its order, duplicate
238 /// headers kept distinct, the resolved `multipart/*` tree, nested
239 /// `message/rfc822`, and the decoded `Content-Transfer-Encoding` constituents. It
240 /// is derived on demand from the exact source (its single dependency is the
241 /// `DocumentExact` root, keyed by `sha256(source)` per ADR-0060). EML has no
242 /// package layer. Derived, never exact.
243 EmlModel = 0x2F,
244 /// The canonical, bounded Parquet model (Phase 21.14): the parsed Thrift-Compact
245 /// footer inventory — the flattened schema (names, physical/logical types,
246 /// repetition), the logical leaf columns, and the row-group/column-chunk
247 /// descriptors, each chunk with its exact source span and statistics. It is
248 /// derived on demand from the exact source (its single dependency is the
249 /// `DocumentExact` root, keyed by `sha256(source)` per ADR-0060). Parquet has no
250 /// package layer. Derived, never exact.
251 ParquetModel = 0x30,
252 /// The canonical, bounded Arrow IPC model (Phase 21.16): the parsed Flatbuffers
253 /// footer (file format) or leading schema message (stream format) inventory —
254 /// the flattened schema (names, type tags/parameters, nullability, children),
255 /// the decodable top-level columns with their pre-computed buffer slots, and the
256 /// record-batch descriptors (each with its exact source span and its declared
257 /// node/buffer counts). It is derived on demand from the exact source (its
258 /// single dependency is the `DocumentExact` root, keyed by `sha256(source)` per
259 /// ADR-0060). Arrow has no package layer. Derived, never exact.
260 ArrowModel = 0x31,
261 /// The canonical, representation-preserving JSON5 / JSONC structured-tree model
262 /// (Phase 21.17.1): the same representation guarantees as [`NodeKind::JsonModel`]
263 /// (exact token spans, member order, duplicate keys, numeric/escape spelling)
264 /// plus the JSON5 superset — unquoted keys, single-quoted strings, trailing
265 /// commas, hex/leading-dot/`Infinity`/`NaN` numbers, string continuations, the
266 /// extended whitespace set, and every comment's exact span — and a recorded
267 /// dialect (`jsonc` vs `json5`). It is derived on demand from the exact source
268 /// (its single dependency is the `DocumentExact` root, keyed by `sha256(source)`
269 /// per ADR-0060). JSON5 has no package layer. Derived, never exact.
270 Json5Model = 0x32,
271 /// The canonical, representation-preserving CBOR structured-tree model (Phase
272 /// 21.18): the same representation guarantees as [`NodeKind::JsonModel`] (exact
273 /// token spans, member order, duplicate keys) plus the CBOR-specific facts — the
274 /// major type of every item, the encoding width actually used (the head byte's
275 /// additional-information nibble), byte-vs-text string as distinct kinds, tag
276 /// numbers preserved verbatim (never resolved/expanded), float width
277 /// (half/single/double) with the IEEE-754 bits, simple values, and definite vs
278 /// indefinite-length items. It is derived on demand from the exact source (its
279 /// single dependency is the `DocumentExact` root, keyed by `sha256(source)` per
280 /// ADR-0060). CBOR has no package layer. Derived, never exact.
281 CborModel = 0x33,
282 /// The canonical, representation-preserving MessagePack structured-tree model
283 /// (Phase 21.19): the same representation guarantees as [`NodeKind::JsonModel`]
284 /// (exact token spans, member order, duplicate keys) plus the MessagePack-specific
285 /// facts — the exact format byte of every item (so the encoding width **and
286 /// signedness** actually used are preserved), `str` vs `bin` as distinct kinds,
287 /// float width (`float32`/`float64`) with the IEEE-754 bits, and extension type
288 /// numbers + payload lengths preserved verbatim (never interpreted). It is derived
289 /// on demand from the exact source (its single dependency is the `DocumentExact`
290 /// root, keyed by `sha256(source)` per ADR-0060). MessagePack has no package layer.
291 /// Derived, never exact.
292 MsgpackModel = 0x34,
293 /// The canonical, representation-preserving config-family model (Phase 21.20):
294 /// the whole source parsed as INI / `.env` / Java `.properties` into a bounded
295 /// arena of lines (exact content spans and terminators) with exact key,
296 /// separator, and value spans per entry, the recorded dialect, the `export`
297 /// marker, quoting and inline-comment facts, `properties` trailing-`\`
298 /// continuations, and `\uXXXX` escapes preserved as spelling. It is computed on
299 /// demand from the exact source (its single dependency is the `DocumentExact`
300 /// root, keyed by `sha256(source)` per ADR-0060); the config family has no
301 /// package layer. Derived, never exact.
302 ConfigModel = 0x35,
303 /// The canonical, representation-preserving RSS 2.0 / Atom 1.0 feed model
304 /// (Phase 21.21): the whole source parsed (via the shared bounded XML parser)
305 /// into a span-preserving element/attribute tree plus the recorded dialect
306 /// (`rss`/`atom`), the channel/feed container node, and the ordered `<item>` /
307 /// `<entry>` record nodes. Every element's and attribute's exact source span,
308 /// element order, attribute spelling (Atom `<link href=… rel=…>`, RSS `<guid
309 /// isPermaLink=…>`), and the Atom namespace declaration are preserved; entity
310 /// references are surfaced literally (never expanded). It is computed on demand
311 /// from the exact source (its single dependency is the `DocumentExact` root,
312 /// keyed by `sha256(source)` per ADR-0060); a feed has no package layer. Derived,
313 /// never exact.
314 FeedModel = 0x36,
315 /// The canonical, representation-preserving GeoJSON (RFC 7946) model
316 /// (Phase 21.22): the whole source parsed by the **shared JSON parser** into a
317 /// span-preserving JSON arena (exact token spans, member order, duplicate keys,
318 /// numeric/escape spelling) plus the GeoJSON semantic anchors — the recorded
319 /// root class (one of the nine RFC 7946 type names), the exact `"type"` token,
320 /// and the geometry and feature object nodes in document order. `coordinates`
321 /// nesting is preserved verbatim (a position is `[lon, lat, (alt)]`, never
322 /// normalized/reordered), and `properties`/`id`/`bbox`/`geometry`/`features`
323 /// order plus every foreign member are preserved. It is computed on demand from
324 /// the exact source (its single dependency is the `DocumentExact` root, keyed by
325 /// `sha256(source)` per ADR-0060); GeoJSON has no package layer. Derived, never
326 /// exact.
327 GeojsonModel = 0x37,
328 /// The canonical, representation-preserving KML 2.2 / GPX 1.1 geospatial model
329 /// (Phase 21.23): the whole source parsed (via the shared bounded XML parser)
330 /// into a span-preserving element/attribute tree plus the recorded dialect
331 /// (`kml`/`gpx`), the ordered record nodes (KML `<Placemark>` features, or GPX
332 /// top-level `<wpt>`/`<rte>`/`<trk>` records), and the ordered point nodes (KML
333 /// `Point`/`LineString`/`Polygon` geometry, or GPX `<wpt>`/`<rtept>`/`<trkpt>`
334 /// point elements). Every element's and attribute's exact source span, element
335 /// order, attribute spelling (KML geometry `coordinates`, GPX `lat`/`lon`), and
336 /// the namespace declaration are preserved; entity references are surfaced
337 /// literally (never expanded). It is computed on demand from the exact source
338 /// (its single dependency is the `DocumentExact` root, keyed by `sha256(source)`
339 /// per ADR-0060); a GIS document has no package layer. Derived, never exact.
340 GisModel = 0x38,
341 /// The canonical, representation-preserving Jupyter notebook (`.ipynb`, nbformat)
342 /// model (Phase 21.24): the whole source parsed by the **shared JSON parser** into a
343 /// span-preserving JSON arena (exact token spans, member order, duplicate keys,
344 /// numeric/escape spelling) plus the notebook semantic anchors — the exact
345 /// `nbformat`/`nbformat_minor` tokens, the root `metadata` object node, and the
346 /// ordered cell anchors. Every cell preserves its exact `cell_type` string
347 /// (`code`/`markdown`/`raw`, or any other), its `source` **exactly as written** (a
348 /// single string or an array of line strings, never re-joined or normalized), its
349 /// `execution_count`/`metadata`/`attachments` when present, and its ordered
350 /// `outputs` with each output's exact `output_type` string
351 /// (`stream`/`execute_result`/`display_data`/`error`, or any other) and every field
352 /// token, including a `stream` `text` and an `execute_result`/`display_data` `data`
353 /// representation. It is computed on demand from the exact source (its single
354 /// dependency is the `DocumentExact` root, keyed by `sha256(source)` per ADR-0060);
355 /// a notebook has no package layer. Derived, never exact.
356 NotebookModel = 0x39,
357 /// The canonical, representation-preserving fixed-width (column-position) model
358 /// (Phase 21.25): the whole source parsed into a bounded table whose columns are
359 /// defined by **character positions** (not a delimiter). It records the inferred
360 /// layout (per-column start/end positions and widths, the uniform record width, the
361 /// line terminator, a BOM, and the mixed-terminator fact), the optional header row
362 /// (record 0), and every record's exact content span and per-column field spans
363 /// (padding preserved). It is computed on demand from the exact source (its single
364 /// dependency is the `DocumentExact` root, keyed by `sha256(source)` per ADR-0060);
365 /// a fixed-width document has no package layer. Derived, never exact.
366 FixedWidthModel = 0x3A,
367 /// The canonical, representation-preserving reStructuredText (Docutils) model
368 /// (Phase 21.26.1): the whole source parsed into a bounded, span-preserving prose
369 /// model — section titles with their exact underline/overline adornment (char and
370 /// length) and a recorded hierarchy, paragraphs, explicit markup (`.. ` comments,
371 /// `.. directive::` directives preserved verbatim, substitution definitions,
372 /// footnotes/citations, hyperlink targets), field/option/definition lists, literal
373 /// (`::`) and doctest (`>>> `) blocks, bullet/enumerated lists with nesting, inline
374 /// spans (strong/emphasis/literal/interpreted/substitution/footnote/hyperlink/
375 /// anonymous references), and grid/simple tables. Every span is exact and the
376 /// source is never re-flowed or normalized. It is computed on demand from the exact
377 /// source (its single dependency is the `DocumentExact` root, keyed by
378 /// `sha256(source)` per ADR-0060); a reST document has no package layer. Derived,
379 /// never exact.
380 RstModel = 0x3B,
381 /// The canonical, representation-preserving AsciiDoc model (Phase 21.26.2): the
382 /// whole source parsed into a bounded, span-preserving prose model — a level-0
383 /// document title and `==`+ sections with their exact `=` marker and recorded
384 /// level, paragraphs, document attributes (`:name: value` / `:name!:`) with
385 /// attribute references (`{name}`) surfaced literally (never expanded), block
386 /// attribute lines attached to the following block, delimited blocks (listing /
387 /// literal / example / sidebar / quote / open / passthrough) with their exact
388 /// delimiter and verbatim content, unordered/ordered/description lists with
389 /// nesting, tables (`|===`), admonitions (`NOTE:` / `[NOTE]`), and inline spans
390 /// (strong/emphasis/mono/passthrough/superscript/subscript/mark plus the `link:`,
391 /// `image:`, `include::`, `xref:` and bare-URL macros, preserved verbatim). Every
392 /// span is exact and the source is never re-flowed or normalized. It is computed
393 /// on demand from the exact source (its single dependency is the `DocumentExact`
394 /// root, keyed by `sha256(source)` per ADR-0060); an AsciiDoc document has no
395 /// package layer. Derived, never exact.
396 AsciidocModel = 0x3C,
397 /// The canonical, representation-preserving MDX (Markdown + JSX/ESM) model
398 /// (Phase 21.26.3): the reused Markdown prose model of the whole source **plus** the
399 /// MDX-specific arenas — top-level ESM `import`/`export` statements, JSX elements
400 /// and fragments with their attributes and nested children (recorded as extents,
401 /// never executed and never parsed as JavaScript), and MDX `{ … }` expressions
402 /// inline and block (brace-balanced with string literals, escapes, and comments
403 /// respected) — and the bit-set of MDX signals observed. Every span is exact and
404 /// the source is never re-flowed or normalized. It is computed on demand from the
405 /// exact source (its single dependency is the `DocumentExact` root, keyed by
406 /// `sha256(source)` per ADR-0060); an MDX document has no package layer. Derived,
407 /// never exact.
408 MdxModel = 0x3D,
409 /// The canonical, representation-preserving MHTML (MIME HTML, RFC 2557) web
410 /// archive model (Phase 21.27): the whole source parsed (via the **reused**
411 /// bounded EML MIME layer) into a span-preserving MIME envelope and ordered parts,
412 /// plus the MHTML-specific anchors — the top-level `Snapshot-Content-Location`/
413 /// `Content-Base`, the ordered sub-resources keyed by `Content-Location`/
414 /// `Content-ID`, and the root HTML part (the `start=` `Content-ID`, else the first
415 /// `text/html` part, else the first part). The root part's **decoded** body is
416 /// parsed by the **reused** bounded HTML scanner into a span-preserving
417 /// [`HtmlModel`](crate::adapter::html::HtmlModel) whose spans are relative to that
418 /// decoded body. It is computed on demand from the exact source (its single
419 /// dependency is the `DocumentExact` root, keyed by `sha256(source)` per
420 /// ADR-0060); MHTML has no package layer. Derived, never exact.
421 MhtmlModel = 0x3E,
422 /// The canonical, representation-preserving syslog / log-stream model (Phase
423 /// 21.28): the whole source split into physical lines, each non-blank line
424 /// classified by a recorded dialect (RFC 5424 syslog, RFC 3164 (BSD) syslog, or a
425 /// generic application log line) into a bounded record with its **exact line
426 /// span** and terminator, its physical line number, its decoded priority (when a
427 /// syslog `<PRI>` is present), the deepest structured-data nesting, and its
428 /// **exact-span fields** (PRI/version/timestamp/hostname/app-name/procid/msgid/
429 /// structured-data element/id/parameter-name/parameter-value/tag/pid/level/msg).
430 /// Messages, NILVALUE (`-`), and timestamp/level spelling are preserved verbatim
431 /// and never normalized. It is computed on demand from the exact source (its
432 /// single dependency is the `DocumentExact` root, keyed by `sha256(source)` per
433 /// ADR-0060); a log stream has no package layer. Derived, never exact.
434 LogstreamModel = 0x3F,
435 /// The canonical, representation-preserving package-metadata model (Phase 21.29):
436 /// the reused JSON/TOML arena (member order, duplicate keys, numeric and
437 /// string-escape spelling, and exact token spans all preserved) **plus** a bounded
438 /// semantic projection — every recorded **section** (a named table /
439 /// array-of-tables / array element in TOML, or an object / array member in JSON)
440 /// with its exact name and value spans, and every recorded key/value **entry** with
441 /// its exact key and value spans, grouped by section in document order, and the
442 /// **recorded dialect** (`npm_package` / `cargo_manifest` / `pyproject_manifest` /
443 /// `cargo_lock` / `npm_lock`). Nothing is normalized or re-serialized. It is
444 /// computed on demand from the exact source (its single dependency is the
445 /// `DocumentExact` root, keyed by `sha256(source)` per ADR-0060); a manifest has no
446 /// package layer. Derived, never exact.
447 PkgmetaModel = 0x40,
448 /// The canonical, representation-preserving API/specification model
449 /// (Phase 21.30): the whole source parsed by the **shared JSON parser** into a
450 /// bounded, representation-preserving JSON arena (exact token spans, member order,
451 /// duplicate keys, numeric/escape spelling) plus the API-spec semantic projection:
452 /// the **recorded dialect** (`json_schema` / `openapi` / `swagger` / `asyncapi`),
453 /// the exact spec-version token, every recorded **object** (with its role, exact
454 /// value span, naming span, and member range), every **member** (exact key/value
455 /// spans), and every **`$ref`** member (target preserved verbatim, never resolved).
456 /// Nothing is normalized, resolved, or re-serialized. It is computed on demand from
457 /// the exact source (its single dependency is the `DocumentExact` root, keyed by
458 /// `sha256(source)` per ADR-0060); an API spec has no package layer. Derived, never
459 /// exact.
460 ApispecModel = 0x41,
461 /// The canonical, representation-preserving bibliography model (Phase 21.31): one
462 /// adapter covering **BibTeX**, **RIS**, and **CSL-JSON**, with the **recorded
463 /// dialect** (`bibtex` / `ris` / `csl_json`). For the text dialects it is the
464 /// entry/field arena (entry key, entry type and field names *as written*, field
465 /// order, every value's exact spelling and delimiters, duplicate-field flags, RIS
466 /// tag separator and multi-line continuation spans); for CSL-JSON it embeds the
467 /// reused JSON arena (member order, duplicate keys, numeric/string-escape spelling,
468 /// exact token spans) plus the item projection (each item's string `id` and `type`
469 /// node). Nothing is normalized or re-serialized. It is computed on demand from the
470 /// exact source (its single dependency is the `DocumentExact` root, keyed by
471 /// `sha256(source)` per ADR-0060); a bibliography has no package layer. Derived,
472 /// never exact.
473 BibModel = 0x42,
474 /// The canonical, representation-preserving RTF (Rich Text Format) model
475 /// (Phase 21.32): the whole source parsed into a bounded, span-preserving
476 /// group-brace model — the header (`\rtfN` version, charset, `\deffN`), every
477 /// group with its exact brace span and nesting depth, every control word (with its
478 /// optional numeric parameter) and control symbol, every `\'hh` hex escape and
479 /// `\uN` Unicode escape (with the active `\ucN` fallback count), every destination
480 /// (`\fonttbl`/`\colortbl`/`\stylesheet`/`\info`/`\pict`/`\object`/`\*`) verbatim,
481 /// and every plain text run byte-exactly (never whitespace-normalized). Nothing is
482 /// normalized or re-serialized. It is computed on demand from the exact source (its
483 /// single dependency is the `DocumentExact` root, keyed by `sha256(source)` per
484 /// ADR-0060); RTF has no package layer. Derived, never exact.
485 RtfModel = 0x43,
486 /// The canonical, representation-preserving generic archive-container model
487 /// (Phase 21.33): one adapter covering **ZIP** and **TAR** with the **recorded
488 /// dialect** (`zip`/`tar`). For ZIP it reuses the byte-authoritative physical
489 /// layer ([`crate::adapter::package::zip`]) and records the ordered
490 /// central-directory member list — raw name bytes (case, UTF-8 flag bit 11, and
491 /// non-UTF-8 bytes preserved), compression method, both declared sizes, CRC-32,
492 /// local-header offset, and each member's exact raw byte span — plus the
493 /// end-of-central-directory record and archive comment. For TAR it records the
494 /// standard 512-byte headers (name/prefix, mode, uid/gid, size, mtime, typeflag,
495 /// linkname, `ustar` magic/version), the old-GNU/v7 variant, the GNU long-name
496 /// (`L`)/long-link (`K`) extensions and the PAX (`x`/`g`) records folded into the
497 /// following member, and every member's exact (512-byte-aligned) span. Nothing is
498 /// unzipped, re-zipped, decompressed, or re-serialized. It is computed on demand
499 /// from the exact source (its single dependency is the `DocumentExact` or
500 /// `PackageRoot` node, keyed by `sha256(source)` per ADR-0060); an archive has no
501 /// separate package layer in this adapter. Derived, never exact.
502 ContainerModel = 0x44,
503 /// The canonical, representation-preserving scientific-container model (Phase
504 /// 21.34): one adapter covering **NetCDF classic** (CDF-1/CDF-2) and a documented
505 /// **bounded subset of HDF5**, with the **recorded dialect** (`netcdf`/`hdf5`). For
506 /// NetCDF it records the magic/`numrecs`, the dimension table (with the unlimited
507 /// dimension), the global attribute list, and the variable list (name, dimensions,
508 /// attributes, type, `vsize`, `begin`) plus the record/array data layout. For HDF5
509 /// it records the superblock, the group/dataset tree reached through the symbol-
510 /// table link traversal, each dataset's datatype / simple-or-scalar dataspace /
511 /// contiguous data layout, and the v1 object-header attribute messages. Everything
512 /// outside the HDF5 subset declines typed. Nothing is decompressed, re-serialized,
513 /// or rewritten. It is computed on demand from the exact source (its single
514 /// dependency is the `DocumentExact` root, keyed by `sha256(source)` per ADR-0060);
515 /// neither dialect has a separate package layer in this adapter. Derived, never
516 /// exact.
517 HdfModel = 0x45,
518 /// The canonical, representation-preserving CFBF / OLE2 compound-file model (Phase
519 /// 21.35): the physical layer behind legacy Word/Excel/PowerPoint/Outlook files,
520 /// with the **recorded dialect** (`cfb3`/`cfb4`). It records the 512-byte header
521 /// (signature, version, byte order, sector/mini-sector shifts, counts, first
522 /// sectors), the directory entries (verbatim UTF-16LE names, object type, color,
523 /// the left/right/child sibling tree, start sector, stream size) with the
524 /// red-black sibling/child tree reconstructed into a `parent`/`depth`, and every
525 /// stream resolved to an exact byte span (regular streams through the FAT, small
526 /// streams through the MiniFAT inside the root entry's mini stream). Nothing is
527 /// un-OLE'd or re-OLE'd. It is computed on demand from the exact source (its single
528 /// dependency is the `DocumentExact` root, keyed by `sha256(source)` per ADR-0060);
529 /// a compound file has no separate package layer in this adapter. Derived, never
530 /// exact.
531 CfbfModel = 0x46,
532 /// The canonical, representation-preserving legacy Word 97–2003 (`.doc`, MS-DOC)
533 /// **semantic** model (Phase 26.1), built on the CFBF/OLE2 physical layer (21.35).
534 /// It **reuses the CFBF resolver** (never a second OLE parser) to obtain the
535 /// `WordDocument` stream and the table stream (`0Table`/`1Table` per the FIB
536 /// `fWhichTblStm` flag), records the Word FIB — `wIdent` `0xA5EC`, the `nFib` with
537 /// its recorded Word version, the `fWhichTblStm` flag, `fcMin`/`fcMac`, the
538 /// `ccpText`/`ccpFtn`/`ccpHdd`/… section counts, and `fcClx`/`lcbClx` — and parses
539 /// the **piece table (CLX)**: the zero-or-more `Prc` (0x01) records, the `Pcdt`
540 /// (0x02) record, and its `PlcPcd` (`n+1` CP values plus `n` 8-byte PCDs). Every
541 /// piece keeps its resolved `fc`, its **compressed** flag (a compressed piece is
542 /// CP1252 single-byte text, an uncompressed piece UTF-16LE), its **exact byte span
543 /// in the `WordDocument` stream**, and its decoded text. Nothing is re-encoded or
544 /// re-flowed. It is computed on demand from the exact source (its single dependency
545 /// is the `DocumentExact` root, keyed by `sha256(source)` per ADR-0060); a `.doc`
546 /// has no separate package layer in this adapter. Derived, never exact.
547 DocModel = 0x47,
548 /// The canonical, representation-preserving legacy Excel 97–2003 (`.xls`, BIFF8)
549 /// **semantic** model (Phase 26.2), built on the CFBF/OLE2 physical layer (21.35).
550 /// It **reuses the CFBF resolver** (never a second OLE parser) to obtain the
551 /// `Workbook` stream (the legacy `Book` name is accepted too), records the BIFF8
552 /// record stream — every `[u16 id][u16 length][payload]` record with its exact byte
553 /// span and stream offset, the `BOF`/`EOF` substream framing, and the record's
554 /// fields — the `BOUNDSHEET` sheets (name, `BOF` stream position, visibility, and
555 /// type, in order), the `SST` shared-string table (with `CONTINUE` reassembly of
556 /// continued strings), and the per-sheet cell records
557 /// (`LABELSST`/`NUMBER`/`RK`/`BOOLERR`/`FORMULA`+`STRING`/`BLANK`/`MULBLANK`) with
558 /// their row/column, exact record span, and exact stored value spelling (`RK`'s
559 /// compact numeric encoding is decoded). `DIMENSIONS`/`ROW`/`FORMAT`/`XF` are
560 /// recorded by span. Nothing is normalized or re-serialized. It is computed on
561 /// demand from the exact source (its single dependency is the `DocumentExact` root,
562 /// keyed by `sha256(source)` per ADR-0060); a `.xls` has no separate package layer
563 /// in this adapter. Derived, never exact.
564 XlsModel = 0x48,
565 /// The canonical, representation-preserving legacy PowerPoint 97–2003 (`.ppt`,
566 /// MS-PPT) **semantic** model (Phase 26.3), built on the CFBF/OLE2 physical layer
567 /// (21.35). It **reuses the CFBF resolver** (never a second OLE parser) to obtain
568 /// the `PowerPoint Document` stream as an exact byte span, records the MS-PPT
569 /// record stream — every 8-byte `RecordHeader` (`recVer`/`recInstance`/`recType`/
570 /// `recLen`) with its exact span, stream offset, and nesting depth — the `Document`
571 /// container, the `SlideListWithText` containers' `SlidePersistAtom` entries
572 /// (slide order by `persistIdRef`), and the `TextCharsAtom` (UTF-16LE) /
573 /// `TextBytesAtom` (CP1252) runs with their exact spans and decoded text; the
574 /// `Current User` stream's `UserEditAtom` offset is recorded when it resolves.
575 /// Nothing is normalized or re-serialized. It is computed on demand from the exact
576 /// source (its single dependency is the `DocumentExact` root, keyed by
577 /// `sha256(source)` per ADR-0060); a `.ppt` has no separate package layer in this
578 /// adapter. Derived, never exact.
579 PptModel = 0x49,
580 /// The canonical, representation-preserving legacy Outlook message (`.msg`,
581 /// MS-OXMSG) **semantic** model (Phase 26.4), built on the CFBF/OLE2 physical layer
582 /// (21.35). It **reuses the CFBF resolver** (never a second OLE parser) to obtain
583 /// the root `__properties_version1.0` stream, the root `__substg1.0_<PPPPTTTT>`
584 /// variable-size streams, the optional `__nameid_version1.0` stream, and the
585 /// recipient (`__recip_version1.0_<id>`) / attachment (`__attach_version1.0_<id>`)
586 /// sub-storages as exact byte spans. It records the message header (`Reserved`,
587 /// `NextRecipientId`, `NextAttachmentId`, `RecipientCount`, `AttachmentCount`, and
588 /// the trailing `Reserved`), the fixed-size 16-byte property entries (raw tag, flags,
589 /// value bytes, exact span, owner) in order, the variable-size substreams with type
590 /// `0x001F` (UTF-16LE) / `0x001E` (ANSI/CP1252) decoded and `0x0102` (binary) kept
591 /// as exact bytes, and every recipient/attachment sub-storage's own property list
592 /// and substreams (order and ids preserved). Nothing is normalized or re-serialized.
593 /// It is computed on demand from the exact source (its single dependency is the
594 /// `DocumentExact` root, keyed by `sha256(source)` per ADR-0060); a `.msg` has no
595 /// separate package layer in this adapter. Derived, never exact.
596 MsgModel = 0x4A,
597}
598
599impl NodeKind {
600 /// Map a raw kind byte.
601 pub const fn from_u8(b: u8) -> Option<NodeKind> {
602 Some(match b {
603 0x01 => NodeKind::DocumentExact,
604 0x02 => NodeKind::SourceSlice,
605 0x03 => NodeKind::PdfRevision,
606 0x04 => NodeKind::PdfObject,
607 0x05 => NodeKind::PdfStreamEncoded,
608 0x06 => NodeKind::PdfStreamDecoded,
609 0x07 => NodeKind::ContentOperators,
610 0x08 => NodeKind::TextRuns,
611 0x09 => NodeKind::PageContent,
612 0x0A => NodeKind::PagePreview,
613 0x0B => NodeKind::ResourceRef,
614 0x0C => NodeKind::Concat,
615 0x0D => NodeKind::Literal,
616 0x0E => NodeKind::PackageRoot,
617 0x0F => NodeKind::PackageMemberRaw,
618 0x10 => NodeKind::PackageMemberDecoded,
619 0x11 => NodeKind::PackageOpcModel,
620 0x12 => NodeKind::DocxModel,
621 0x13 => NodeKind::DocxStory,
622 0x14 => NodeKind::EpubModel,
623 0x15 => NodeKind::EpubContent,
624 0x16 => NodeKind::ResourceBlob,
625 0x17 => NodeKind::OdtModel,
626 0x18 => NodeKind::OdtContent,
627 0x19 => NodeKind::PdfRevisionLineage,
628 0x1A => NodeKind::XlsxModel,
629 0x1B => NodeKind::XlsxWorkbook,
630 0x1C => NodeKind::XlsxSheet,
631 0x1D => NodeKind::PptxModel,
632 0x1E => NodeKind::PptxPresentation,
633 0x1F => NodeKind::PptxSlide,
634 0x20 => NodeKind::PptxNotes,
635 0x21 => NodeKind::OdsModel,
636 0x22 => NodeKind::OdsContent,
637 0x23 => NodeKind::OdsStyles,
638 0x24 => NodeKind::OdpModel,
639 0x25 => NodeKind::OdpContent,
640 0x26 => NodeKind::OdpStyles,
641 0x27 => NodeKind::JsonModel,
642 0x28 => NodeKind::YamlModel,
643 0x29 => NodeKind::CsvModel,
644 0x2A => NodeKind::MarkdownModel,
645 0x2B => NodeKind::XmlModel,
646 0x2C => NodeKind::HtmlModel,
647 0x2D => NodeKind::TomlModel,
648 0x2E => NodeKind::JsonlModel,
649 0x2F => NodeKind::EmlModel,
650 0x30 => NodeKind::ParquetModel,
651 0x31 => NodeKind::ArrowModel,
652 0x32 => NodeKind::Json5Model,
653 0x33 => NodeKind::CborModel,
654 0x34 => NodeKind::MsgpackModel,
655 0x35 => NodeKind::ConfigModel,
656 0x36 => NodeKind::FeedModel,
657 0x37 => NodeKind::GeojsonModel,
658 0x38 => NodeKind::GisModel,
659 0x39 => NodeKind::NotebookModel,
660 0x3A => NodeKind::FixedWidthModel,
661 0x3B => NodeKind::RstModel,
662 0x3C => NodeKind::AsciidocModel,
663 0x3D => NodeKind::MdxModel,
664 0x3E => NodeKind::MhtmlModel,
665 0x3F => NodeKind::LogstreamModel,
666 0x40 => NodeKind::PkgmetaModel,
667 0x41 => NodeKind::ApispecModel,
668 0x42 => NodeKind::BibModel,
669 0x43 => NodeKind::RtfModel,
670 0x44 => NodeKind::ContainerModel,
671 0x45 => NodeKind::HdfModel,
672 0x46 => NodeKind::CfbfModel,
673 0x47 => NodeKind::DocModel,
674 0x48 => NodeKind::XlsModel,
675 0x49 => NodeKind::PptModel,
676 0x4A => NodeKind::MsgModel,
677 _ => return None,
678 })
679 }
680
681 /// Stable short name.
682 pub const fn name(self) -> &'static str {
683 match self {
684 NodeKind::DocumentExact => "DocumentExact",
685 NodeKind::SourceSlice => "SourceSlice",
686 NodeKind::PdfRevision => "PdfRevision",
687 NodeKind::PdfObject => "PdfObject",
688 NodeKind::PdfStreamEncoded => "PdfStreamEncoded",
689 NodeKind::PdfStreamDecoded => "PdfStreamDecoded",
690 NodeKind::ContentOperators => "ContentOperators",
691 NodeKind::TextRuns => "TextRuns",
692 NodeKind::PageContent => "PageContent",
693 NodeKind::PagePreview => "PagePreview",
694 NodeKind::ResourceRef => "ResourceRef",
695 NodeKind::Concat => "Concat",
696 NodeKind::Literal => "Literal",
697 NodeKind::PackageRoot => "PackageRoot",
698 NodeKind::PackageMemberRaw => "PackageMemberRaw",
699 NodeKind::PackageMemberDecoded => "PackageMemberDecoded",
700 NodeKind::PackageOpcModel => "PackageOpcModel",
701 NodeKind::DocxModel => "DocxModel",
702 NodeKind::DocxStory => "DocxStory",
703 NodeKind::EpubModel => "EpubModel",
704 NodeKind::EpubContent => "EpubContent",
705 NodeKind::ResourceBlob => "ResourceBlob",
706 NodeKind::OdtModel => "OdtModel",
707 NodeKind::OdtContent => "OdtContent",
708 NodeKind::PdfRevisionLineage => "PdfRevisionLineage",
709 NodeKind::XlsxModel => "XlsxModel",
710 NodeKind::XlsxWorkbook => "XlsxWorkbook",
711 NodeKind::XlsxSheet => "XlsxSheet",
712 NodeKind::PptxModel => "PptxModel",
713 NodeKind::PptxPresentation => "PptxPresentation",
714 NodeKind::PptxSlide => "PptxSlide",
715 NodeKind::PptxNotes => "PptxNotes",
716 NodeKind::OdsModel => "OdsModel",
717 NodeKind::OdsContent => "OdsContent",
718 NodeKind::OdsStyles => "OdsStyles",
719 NodeKind::OdpModel => "OdpModel",
720 NodeKind::OdpContent => "OdpContent",
721 NodeKind::OdpStyles => "OdpStyles",
722 NodeKind::JsonModel => "JsonModel",
723 NodeKind::YamlModel => "YamlModel",
724 NodeKind::CsvModel => "CsvModel",
725 NodeKind::MarkdownModel => "MarkdownModel",
726 NodeKind::XmlModel => "XmlModel",
727 NodeKind::HtmlModel => "HtmlModel",
728 NodeKind::TomlModel => "TomlModel",
729 NodeKind::JsonlModel => "JsonlModel",
730 NodeKind::EmlModel => "EmlModel",
731 NodeKind::ParquetModel => "ParquetModel",
732 NodeKind::ArrowModel => "ArrowModel",
733 NodeKind::Json5Model => "Json5Model",
734 NodeKind::CborModel => "CborModel",
735 NodeKind::MsgpackModel => "MsgpackModel",
736 NodeKind::ConfigModel => "ConfigModel",
737 NodeKind::FeedModel => "FeedModel",
738 NodeKind::GeojsonModel => "GeojsonModel",
739 NodeKind::GisModel => "GisModel",
740 NodeKind::NotebookModel => "NotebookModel",
741 NodeKind::FixedWidthModel => "FixedWidthModel",
742 NodeKind::RstModel => "RstModel",
743 NodeKind::AsciidocModel => "AsciidocModel",
744 NodeKind::MdxModel => "MdxModel",
745 NodeKind::MhtmlModel => "MhtmlModel",
746 NodeKind::LogstreamModel => "LogstreamModel",
747 NodeKind::PkgmetaModel => "PkgmetaModel",
748 NodeKind::ApispecModel => "ApispecModel",
749 NodeKind::BibModel => "BibModel",
750 NodeKind::RtfModel => "RtfModel",
751 NodeKind::ContainerModel => "ContainerModel",
752 NodeKind::HdfModel => "HdfModel",
753 NodeKind::CfbfModel => "CfbfModel",
754 NodeKind::DocModel => "DocModel",
755 NodeKind::XlsModel => "XlsModel",
756 NodeKind::PptModel => "PptModel",
757 NodeKind::MsgModel => "MsgModel",
758 }
759 }
760
761 /// Whether the output bytes are an exact, byte-identical observation of the
762 /// source (`true`) or a deterministic derived projection (`false`).
763 ///
764 /// This is the `Q_ref` / `Q_gen` boundary (ADR-0026).
765 pub const fn is_exact(self) -> bool {
766 matches!(
767 self,
768 NodeKind::DocumentExact
769 | NodeKind::SourceSlice
770 | NodeKind::PdfRevision
771 | NodeKind::PdfObject
772 | NodeKind::PdfStreamEncoded
773 | NodeKind::ResourceRef
774 | NodeKind::Concat
775 | NodeKind::Literal
776 // Package physical leaves are exact source spans; a decoded
777 // member (`PackageMemberDecoded`) is derived and is **not** exact.
778 | NodeKind::PackageRoot
779 | NodeKind::PackageMemberRaw
780 // A shared resource is the exact embedded bytes.
781 | NodeKind::ResourceBlob
782 )
783 }
784}
785
786/// A bounded resource envelope declared by a node.
787#[derive(Debug, Clone, Copy, PartialEq, Eq)]
788pub struct NodeLimits {
789 /// Maximum materialized output length this node may produce.
790 pub max_output_bytes: u64,
791 /// Maximum dependency depth permitted below this node.
792 pub max_depth: u16,
793 /// Maximum dependency fanout permitted.
794 pub max_fanout: u16,
795}
796
797impl NodeLimits {
798 /// Conservative defaults used by the PDF inverse compiler.
799 pub const DEFAULT: NodeLimits = NodeLimits {
800 max_output_bytes: 1 << 31,
801 max_depth: 64,
802 max_fanout: MAX_NODE_DEPS as u16,
803 };
804}
805
806/// One canonical procedural seed node.
807#[derive(Debug, Clone, PartialEq, Eq)]
808pub struct SeedNode {
809 /// Computation kind.
810 pub kind: NodeKind,
811 /// Materializer registry id (the kind value for v1).
812 pub materializer_id: u16,
813 /// Materializer semantics version.
814 pub materializer_version: u16,
815 /// Declared logical output length (a bound, checked on materialization).
816 pub logical_output_len: u64,
817 /// Kind-specific canonical parameters.
818 pub params: Vec<u8>,
819 /// Canonical dependency ids actually read (the dynamic read set).
820 pub deps: Vec<NodeId>,
821 /// Short provenance/basis string (adapter-supplied, advisory).
822 pub provenance: String,
823 /// Resource envelope.
824 pub limits: NodeLimits,
825}
826
827impl SeedNode {
828 /// Construct a node with the current materializer version.
829 pub fn new(
830 kind: NodeKind,
831 logical_output_len: u64,
832 params: Vec<u8>,
833 deps: Vec<NodeId>,
834 provenance: impl Into<String>,
835 ) -> Self {
836 SeedNode {
837 kind,
838 materializer_id: kind as u16,
839 materializer_version: MATERIALIZER_VERSION,
840 logical_output_len,
841 params,
842 deps,
843 provenance: provenance.into(),
844 limits: NodeLimits::DEFAULT,
845 }
846 }
847
848 /// Canonically encode this node. The encoding never includes the node id.
849 pub fn encode_canonical(&self) -> Vec<u8> {
850 let mut out = Vec::with_capacity(64 + self.params.len());
851 out.push(NODE_MAGIC);
852 out.push(crate::store::SEED_FORMAT_VERSION);
853 out.push(self.kind as u8);
854 out.push(0); // reserved
855 out.extend_from_slice(&self.materializer_id.to_le_bytes());
856 out.extend_from_slice(&self.materializer_version.to_le_bytes());
857 out.extend_from_slice(&self.logical_output_len.to_le_bytes());
858 out.extend_from_slice(&(self.deps.len() as u32).to_le_bytes());
859 out.extend_from_slice(&(self.params.len() as u32).to_le_bytes());
860 out.extend_from_slice(&self.limits.max_output_bytes.to_le_bytes());
861 out.extend_from_slice(&self.limits.max_depth.to_le_bytes());
862 out.extend_from_slice(&self.limits.max_fanout.to_le_bytes());
863 for dep in &self.deps {
864 out.extend_from_slice(dep.as_bytes());
865 }
866 out.extend_from_slice(&self.params);
867 let prov = self.provenance.as_bytes();
868 out.extend_from_slice(&(prov.len() as u32).to_le_bytes());
869 out.extend_from_slice(prov);
870 out
871 }
872
873 /// Parse a canonical node, enforcing structural bounds.
874 pub fn decode_canonical(bytes: &[u8]) -> Result<SeedNode> {
875 let mut r = Reader::new(bytes);
876 if r.u8()? != NODE_MAGIC {
877 return Err(Error::unsupported_version("seed node: bad magic"));
878 }
879 let version = r.u8()?;
880 if version != crate::store::SEED_FORMAT_VERSION {
881 return Err(Error::unsupported_version(format!(
882 "seed node format version {version} is not supported"
883 )));
884 }
885 let kind_byte = r.u8()?;
886 let kind = NodeKind::from_u8(kind_byte)
887 .ok_or_else(|| Error::unsupported_version(format!("unknown node kind {kind_byte}")))?;
888 let _reserved = r.u8()?;
889 let materializer_id = r.u16()?;
890 let materializer_version = r.u16()?;
891 let logical_output_len = r.u64()?;
892 let dep_count = r.u32()? as usize;
893 let param_len = r.u32()? as usize;
894 let max_output_bytes = r.u64()?;
895 let max_depth = r.u16()?;
896 let max_fanout = r.u16()?;
897 if dep_count > MAX_NODE_DEPS {
898 return Err(Error::resource_limit(format!(
899 "seed node declares {dep_count} deps (max {MAX_NODE_DEPS})"
900 )));
901 }
902 let mut deps = Vec::with_capacity(dep_count);
903 for _ in 0..dep_count {
904 let mut b = [0u8; 32];
905 b.copy_from_slice(r.bytes(32)?);
906 deps.push(NodeId::from_bytes(b));
907 }
908 let params = r.bytes(param_len)?.to_vec();
909 let prov_len = r.u32()? as usize;
910 let prov = r.bytes(prov_len)?;
911 let provenance = core::str::from_utf8(prov)
912 .map_err(|_| Error::usage("seed node provenance is not UTF-8"))?
913 .to_string();
914 if !r.at_end() {
915 return Err(Error::usage("seed node has trailing bytes"));
916 }
917 if materializer_id != kind as u16 {
918 return Err(Error::unsupported_version(
919 "seed node materializer id does not match its kind",
920 ));
921 }
922 Ok(SeedNode {
923 kind,
924 materializer_id,
925 materializer_version,
926 logical_output_len,
927 params,
928 deps,
929 provenance,
930 limits: NodeLimits {
931 max_output_bytes,
932 max_depth,
933 max_fanout,
934 },
935 })
936 }
937
938 /// The content id of this node's canonical encoding.
939 pub fn content_id(&self) -> NodeId {
940 NodeId::of_node(&self.encode_canonical())
941 }
942
943 /// Validate the declared envelope against the global [`Limits`].
944 pub fn check_limits(&self, limits: &Limits) -> Result<()> {
945 if self.logical_output_len > self.limits.max_output_bytes {
946 return Err(Error::resource_limit(format!(
947 "seed node declares output {} > its own cap {}",
948 self.logical_output_len, self.limits.max_output_bytes
949 )));
950 }
951 let _ = limits;
952 Ok(())
953 }
954}
955
956/// A tiny bounds-checked little-endian reader.
957struct Reader<'a> {
958 b: &'a [u8],
959 at: usize,
960}
961
962impl<'a> Reader<'a> {
963 fn new(b: &'a [u8]) -> Self {
964 Reader { b, at: 0 }
965 }
966 fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
967 let end = self
968 .at
969 .checked_add(n)
970 .ok_or_else(|| Error::usage("seed node read overflow"))?;
971 if end > self.b.len() {
972 return Err(Error::usage("truncated seed node"));
973 }
974 let s = &self.b[self.at..end];
975 self.at = end;
976 Ok(s)
977 }
978 fn u8(&mut self) -> Result<u8> {
979 Ok(self.bytes(1)?[0])
980 }
981 fn u16(&mut self) -> Result<u16> {
982 let b = self.bytes(2)?;
983 Ok(u16::from_le_bytes([b[0], b[1]]))
984 }
985 fn u32(&mut self) -> Result<u32> {
986 let b = self.bytes(4)?;
987 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
988 }
989 fn u64(&mut self) -> Result<u64> {
990 let b = self.bytes(8)?;
991 let mut a = [0u8; 8];
992 a.copy_from_slice(b);
993 Ok(u64::from_le_bytes(a))
994 }
995 fn at_end(&self) -> bool {
996 self.at == self.b.len()
997 }
998}
999
1000/// Encode an `[offset, len]` parameter block.
1001pub fn span_params(offset: u64, len: u64) -> Vec<u8> {
1002 let mut p = Vec::with_capacity(16);
1003 p.extend_from_slice(&offset.to_le_bytes());
1004 p.extend_from_slice(&len.to_le_bytes());
1005 p
1006}
1007
1008/// Decode an `[offset, len]` parameter block.
1009pub fn read_span_params(params: &[u8]) -> Result<(u64, u64)> {
1010 if params.len() != 16 {
1011 return Err(Error::usage("span params must be 16 bytes"));
1012 }
1013 let mut a = [0u8; 8];
1014 a.copy_from_slice(¶ms[0..8]);
1015 let offset = u64::from_le_bytes(a);
1016 a.copy_from_slice(¶ms[8..16]);
1017 let len = u64::from_le_bytes(a);
1018 Ok((offset, len))
1019}
1020
1021/// Encode a `(u32, u16, ...)` object-identity parameter block: `object`,
1022/// `generation`, then a trailing kind-specific `u64`.
1023pub fn object_params(object: u32, generation: u16, extra: u64) -> Vec<u8> {
1024 let mut p = Vec::with_capacity(16);
1025 p.extend_from_slice(&object.to_le_bytes());
1026 p.extend_from_slice(&generation.to_le_bytes());
1027 p.extend_from_slice(&0u16.to_le_bytes());
1028 p.extend_from_slice(&extra.to_le_bytes());
1029 p
1030}
1031
1032/// Decode an object-identity parameter block.
1033pub fn read_object_params(params: &[u8]) -> Result<(u32, u16, u64)> {
1034 if params.len() != 16 {
1035 return Err(Error::usage("object params must be 16 bytes"));
1036 }
1037 let object = u32::from_le_bytes([params[0], params[1], params[2], params[3]]);
1038 let generation = u16::from_le_bytes([params[4], params[5]]);
1039 let mut a = [0u8; 8];
1040 a.copy_from_slice(¶ms[8..16]);
1041 let extra = u64::from_le_bytes(a);
1042 Ok((object, generation, extra))
1043}
1044
1045/// Encode a single `u32` parameter.
1046pub fn u32_params(v: u32) -> Vec<u8> {
1047 v.to_le_bytes().to_vec()
1048}
1049
1050/// Decode a single `u32` parameter.
1051pub fn read_u32_params(params: &[u8]) -> Result<u32> {
1052 if params.len() != 4 {
1053 return Err(Error::usage("u32 params must be 4 bytes"));
1054 }
1055 Ok(u32::from_le_bytes([
1056 params[0], params[1], params[2], params[3],
1057 ]))
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062 use super::*;
1063
1064 #[test]
1065 fn canonical_roundtrip_is_stable() {
1066 let n = SeedNode::new(
1067 NodeKind::PdfStreamDecoded,
1068 4096,
1069 span_params(100, 200),
1070 vec![NodeId::from_bytes([7u8; 32])],
1071 "pdf:stream-decoded",
1072 );
1073 let enc = n.encode_canonical();
1074 let back = SeedNode::decode_canonical(&enc).unwrap();
1075 assert_eq!(n, back);
1076 assert_eq!(back.content_id(), n.content_id());
1077 assert_eq!(n.encode_canonical(), enc);
1078 }
1079
1080 #[test]
1081 fn dependency_change_changes_id() {
1082 let a = SeedNode::new(
1083 NodeKind::Concat,
1084 10,
1085 vec![],
1086 vec![NodeId::from_bytes([1u8; 32])],
1087 "t",
1088 );
1089 let b = SeedNode::new(
1090 NodeKind::Concat,
1091 10,
1092 vec![],
1093 vec![NodeId::from_bytes([2u8; 32])],
1094 "t",
1095 );
1096 assert_ne!(a.content_id(), b.content_id());
1097 }
1098
1099 #[test]
1100 fn unknown_kind_and_version_fail_closed() {
1101 let mut n = SeedNode::new(NodeKind::Literal, 1, vec![9], vec![], "t").encode_canonical();
1102 n[0] = 0x00;
1103 assert!(SeedNode::decode_canonical(&n).is_err());
1104 let mut n2 = SeedNode::new(NodeKind::Literal, 1, vec![9], vec![], "t").encode_canonical();
1105 n2[1] = 0x7F;
1106 assert!(SeedNode::decode_canonical(&n2).is_err());
1107 }
1108
1109 #[test]
1110 fn trailing_bytes_are_rejected() {
1111 let mut enc = SeedNode::new(NodeKind::Literal, 1, vec![], vec![], "t").encode_canonical();
1112 enc.push(0);
1113 assert!(SeedNode::decode_canonical(&enc).is_err());
1114 }
1115
1116 #[test]
1117 fn param_helpers_roundtrip() {
1118 let (o, l) = read_span_params(&span_params(5, 9)).unwrap();
1119 assert_eq!((o, l), (5, 9));
1120 let (ob, g, e) = read_object_params(&object_params(42, 3, 77)).unwrap();
1121 assert_eq!((ob, g, e), (42, 3, 77));
1122 assert_eq!(read_u32_params(&u32_params(1234)).unwrap(), 1234);
1123 }
1124}