pdf_merge
Merge bounded PDF path or artifact sources sequentially into one atomically published PDF.
Parameters
- filesarrayone of
files/source_key - Non-empty array of paths, artifact descriptors, or canonical artifact URIs. Path strings support
${ctx.key}interpolation. - source_keystringone of
files/source_key - Context key containing the non-empty source array. Useful when prior nodes produced artifact descriptors.
- output_pathstringrequired
- Merged output path with
${ctx.key}interpolation. - output_keystringdefault "pdf_merge"
- Prefix for output values.
files and source_key are mutually exclusive.
Resource and durability contract
| Environment variable | Default | Boundary |
|---|---|---|
IRONFLOW_MAX_PDF_MERGE_FILES | 100 | Source entries admitted before collection |
IRONFLOW_MAX_PDF_BYTES | 104857600 | Bytes in each source PDF |
IRONFLOW_MAX_PDF_DECOMPRESSED_STREAM_BYTES | 67108864 | Decoded bytes per object/cross-reference stream in each source |
IRONFLOW_MAX_PDF_OBJECTS | 250000 | Loaded objects per source, checked after parsing |
IRONFLOW_MAX_PDF_MERGE_BYTES | 536870912 | Cumulative input bytes and staged output bytes |
IRONFLOW_MAX_PDF_MERGE_PAGES | 2000 | Cumulative pages |
IRONFLOW_MAX_PDF_MERGE_OBJECTS | 250000 | Retained output graph objects |
Inputs are opened and parsed one at a time on a tracked blocking worker. For each source, the union of objects reachable from all selected pages is remapped once, so pages sharing fonts, images, or resources do not clone those objects per page. Artifact inputs use the same verified file handle used for identity checking.
Every source passes the shared PDF loading policy, including strict parsing and bounded recovery checks, before graph collection. The loaded object limit includes objects not needed in the merged graph; the retained-output object limit remains separate. Neither is a hard parser memory ceiling.
Before replacing page parents, the node materializes the nearest inherited
Resources, MediaBox, CropBox, and Rotate values. Explicit page values
take precedence; null values are treated as absent. Inherited indirect resources
remain shared and count toward the retained-object limit. Broken or cyclic
parent chains fail without replacing an existing output file.
Links between retained pages and annotation backlinks are remapped. Source catalog/page-tree references are disconnected rather than copying the old tree. This is a page-oriented merge, not preservation of document-level outlines, signatures, catalog features, or a general content-redaction operation.
The result is written to a sibling staged file, flushed and synchronized, then atomically committed. Parse, limit, save, and cancellation failures remove the partial staging file and preserve an existing destination. The output refuses a final link or non-regular destination.
Configured parent-directory aliases for output_path, including macOS /tmp,
are resolved before staging and missing parent directories are created. Unix
pins the resolved directory, so retargeting the alias afterward cannot redirect
the write. This is not permission to follow a destination-file symlink.
Artifact inputs require a protected artifact directory visible at the same path on every eligible worker. A shared mount is not an authentication boundary against a process running under the same OS identity.
Context output
<output_key>_path— merged PDF path.<output_key>_page_count— total page count.<output_key>_success—trueafter commit.
Example
local flow = Flow.new("merge_artifacts")
flow:step("sources", function(ctx)
return { merge_sources = { ctx.first_artifact, ctx.second_artifact } }
end)
flow:step("merge", nodes.pdf_merge({
source_key = "merge_sources",
output_path = "/tmp/full-report.pdf"
})):depends_on("sources")
return flow