Bundle Creation
The BundleWriter assembles all serialized artifacts, the manifest, and README into a ZIP archive — producing the final deliverable of the export pipeline.
Bundle Assembly Sequence
Serialize Each Artifact
The PackageSerializer converts each artifact entity from the database into its JSON representation. Each artifact type has a dedicated serializer that produces stable, deterministic output (keys sorted alphabetically to ensure consistent checksums).
Compute Per-Artifact Hashes
SHA-256 is computed over the UTF-8 bytes of each artifact JSON file. These per-artifact hashes are stored in the manifest and used by the import engine to verify individual files.
Compute Bundle Checksum
The overall bundle checksum is SHA-256 of all artifact bytes concatenated in install order. This single checksum covers the entire content of the bundle.
Generate manifest.json
The manifest is generated with all artifact entries, hashes, and the bundle checksum. The manifest is then serialized to JSON.
Generate README.md
An auto-generated README is created listing the package name, version, artifacts, and installation prerequisites.
Write ZIP
BundleWriter creates the ZIP archive using System.IO.Compression.ZipArchive in memory. All artifact files are written under artifacts/{type}/; the manifest and README are written at the root.
Store and Return
The ZIP bytes are stored in blob storage and a download URL is generated. The API response returns the download URL, package ID, and manifest summary.
Artifact Serialization Per Type
ProcessDefinition Serialization
// artifacts/workflows/proc-1001.json
{
"_type": "ProcessDefinition",
"_version": "2.1.0",
"_exportedAt": "2026-05-25T09:00:00Z",
"id": "proc-1001",
"name": "EmployeeOnboarding",
"description": "Multi-step employee onboarding with approval",
"nodes": [
{
"nodeId": "n1",
"nodeType": "StartNode",
"config": {}
},
{
"nodeId": "n2",
"nodeType": "FormDisplayNode",
"config": { "formId": "form-2005" }
},
{
"nodeId": "n3",
"nodeType": "RuleEvaluationNode",
"config": { "ruleSetId": "rule-305" }
}
],
"connections": [
{ "from": "n1", "to": "n2" },
{ "from": "n2", "to": "n3" }
]
}
AtlasForm Serialization
// artifacts/forms/form-2005.json
{
"_type": "AtlasForm",
"_version": "1.3.0",
"_exportedAt": "2026-05-25T09:00:00Z",
"id": "form-2005",
"name": "EmployeeForm",
"fields": [
{
"fieldId": "f1",
"fieldType": "TextInput",
"label": "First Name",
"required": true,
"validation": { "maxLength": 100 }
},
{
"fieldId": "f2",
"fieldType": "Dropdown",
"label": "Department",
"dataSource": { "type": "EntityQuery", "entityId": "ent-44", "field": "departments" }
}
],
"actions": [
{ "actionId": "submit", "type": "FormSubmit", "label": "Submit" }
]
}
ZIP Structure
employee-onboarding-2.1.0.zip
├── manifest.json (root)
├── README.md (root)
└── artifacts/
├── workflows/
│ ├── proc-1001.json
│ └── thread-2002.json
├── forms/
│ └── form-2005.json
├── rules/
│ └── rule-305.json
└── entities/
└── ent-44.json
ZIP Compression Settings
The bundle uses CompressionLevel.Optimal for artifact JSON files (high text compressibility). The manifest is stored uncompressed (CompressionLevel.NoCompression) to allow fast extraction without decompressing the entire archive.
BundleWriter Implementation
public class BundleWriter
{
public async Task<byte[]> WriteAsync(
IEnumerable<SerializedArtifact> artifacts,
PackageManifest manifest,
CancellationToken ct = default)
{
using var ms = new MemoryStream();
using var zip = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true);
// Write artifact files
foreach (var artifact in artifacts)
{
var entry = zip.CreateEntry(artifact.FilePath, CompressionLevel.Optimal);
await using var stream = entry.Open();
await stream.WriteAsync(artifact.Bytes, ct);
}
// Write manifest (no compression — fast to read)
var manifestEntry = zip.CreateEntry("manifest.json", CompressionLevel.NoCompression);
await using var manifestStream = manifestEntry.Open();
await JsonSerializer.SerializeAsync(manifestStream, manifest, _jsonOptions, ct);
// Write README
var readmeEntry = zip.CreateEntry("README.md", CompressionLevel.Optimal);
await using var readmeStream = readmeEntry.Open();
await readmeStream.WriteAsync(GenerateReadme(manifest), ct);
zip.Dispose();
return ms.ToArray();
}
}