Your export is not portable until it works without your app

Design data packages that preserve meaning, expose omissions, and can be checked after the service is gone.
The download finishes. Inside the ZIP: a JSON file, a handful of identifiers, and attachment URLs that require a session cookie.
The export button worked. The handoff did not.
A useful export lets someone do meaningful work after leaving the application. They should be able to identify the records, understand their relationships, open the included files, and discover what is missing. If those tasks still require your API, the package is a reference to your service, not an independent copy of the work.
This is a product-engineering problem before it is a storage problem. More durable hosting cannot explain an undocumented status code.
Consider a hypothetical project workspace containing records, comments, and attachments. The design below is a proposed export contract—not a claim about a shipped Antler Labs implementation.
Define what the package promises
“Export everything” sounds clear until someone asks whether that includes deleted comments, private notes, prior attachment versions, or records outside the caller’s permissions.
Writhe scope before choosing a file format.
For this workspace, a reasonable first contract might be: export the current records the caller is allowed to access, their visible comments, and the specific attachment versions those records reference. Exclude credentials and private notes outside that scope. Describe intentional exclusions without leaking the existence or titles of inaccessible records.
Then separate three questions:
- Coverage: did the exporter include everything promised by that scope?
- Integrity: do the files still match the bytes described in the package?
- Interpretability: can another reader understand and use those files?
A checksum cannot establish coverage. A perfectly intact export can omit half the workspace. A complete set of database rows can still be unusable if no one knows what the fields mean.
Give each question its own check.
Export meaning alongside values
An internal database schema is optimized for running your application. An export schema is an interface for someone who may never run it.
Preserve stable identifiers so relationships survive the handoff. Document what identifiers refer to, not just their types. Specify timestamp zones, units, enumeration values, null behavior, and whether a missing field differs from an explicitly empty value.
For a first version of the hypothetical package, keep the layout small:
| File or directory | Responsibility |
|---|---|
README.md | Explain scope, exclusions, extraction, and verification. |
schema.json | Describe the versioned record structure and field meanings. |
records.jsonl | Store one UTF-8 JSON record per line, with stable IDs. |
comments.jsonl | Preserve comment IDs and their parent record IDs. |
attachments/ | Include the attachment bytes promised by the contract. |
manifest.json | Inventory payload paths, byte lengths, digests, and export metadata. |

Use relative paths inside the package. Keep original filenames as metadata when necessary, while assigning safe, unambiguous package paths. Two attachments called report.pdf must not overwrite each other.
Make the format version explicit. Define how readers should handle unknown fields and unsupported versions. A reader that silently guesses what a new enum means is harder to trust than one that reports a clear incompatibility.
The names above are illustrative, not an existing standard. If you adopt a standard package format, follow its actual requirements rather than borrowing the vocabulary and claiming compatibility.
Choose a snapshot boundary
A long-running export can accidentally assemble a workspace that never existed.
Imagine reading the records first, then the comments. Between those reads, someone deletes a record and moves a comment. Separately successful queries do not guarantee a coherent handoff.
In PostgreSQL, successive queries in one Repeatable Read transaction use a stable snapshot rather than acquiring a new snapshot for each statement. That can help coordinate database reads, but it does not freeze an external object store or guarantee every application-level invariant. The details are in the transaction isolation documentation.
For the proposed design, capture a stable inventory of records and immutable attachment-version references. Copy those referenced bytes, then verify the package against the inventory. Avoid holding a database transaction open for an entire slow download without considering its operational cost.
If an attachment cannot be retrieved, choose an honest outcome: fail the export, or produce an explicitly partial package with an omission report. Do not label it complete merely because archive creation succeeded.
Record the consistency model in the metadata. A single database snapshot and a best-effort collection assembled over ten minutes are different promises.
Make verification smaller than the application
The package should carry enough information for an independent reader to check its structure and file inventory.
BagIt, described in RFC 8493, is a useful reference: it separates payload files from packaging metadata and uses manifests that map paths to checksums. Its distinction between a complete bag and a valid bag makes missing files and failed checksums separate concerns.
You do not need to adopt BagIt for every product. You do need an equally explicit answer to “what should be here?”
For the example contract, a verifier would reject duplicate paths, check every listed payload file, compare byte lengths and digests, and report unlisted payload files. Define which metadata files sit outside that payload inventory; a manifest cannot naively contain its own final digest.
Check relationships too. Every exported comment should point to an included record, unless the schema explicitly permits an external refee. File integrity alone will not catch a broken parent ID.
Treat the archive as untrusted input. Enforce path and size limits, reject paths that escape the extraction directory, and define how symbolic links and case-colliding filenames are handled. Verification should not require executing code found inside an unknown package.
Be precise about what a digest proves
For a simple export, hash the exact file bytes. Then whitespace changes, line-ending conversions, or reformatting produce a mismatch by design. That is useful when the question is whether the file changed.
If the contract instead requires equivalent JSON representations to produce the same digest, serialization becomes part of the protocol. RFC 8785 specifies a JSON Canonicalization Scheme with deterministic property ordering and serialization rules. It also constrains inputs; sorting object keys is not a complete implementation.
Choose one rule and document it. Do not let one reader hash original bytes while another hashes parsed-and-reserialized JSON.
Also avoid turning a checksum result into an authenticity claim. An attacker who replaces both the payload and its unsigned manifest can make them agree. BagIt explicitly distinguishes corruption detection from protection against active attacks and points to additional measures such as digital signatures.
If authenticity matters, define what is signed and how the verifier establishes trust in the signing key. A public key bundled with the archive is not, by itself, proof that the archive came from the organization named inside it. The key-origin authentication guidance in RFC 7515 makes that trust requirement explicit for JSON Web Signatures.
A useful report distinguishes “files match the manifest” from “signature checked against a trusted key.” Neither result proves that every statement in the files is true.
Make the offline test part of release

Prepare a clean environment with documented reader tools installed, no application credentials, and network access disabled. Hand it a representative export.
Can the reader identify the format version, enumerate the records, follow their relationships, open attachments, and validate the inventory? Can it explain omissions without calling home?
Then deliberately break copies of the package:
| Change | Expected response |
|---|---|
| Remove a listed attachment | Report the missing file; do not claim a complete package. |
| Change one payload byte | Report an integrity mismatch. |
| Add a duplicate or unsafe path | Reject the ambiguous or unsafe package. |
| Change a comment’s parent ID and update its file digest | Detect the relationship error, despite matching file checksums. |
| Present an unsupported schema version | Report incompatibility instead of inventing an interpretation. |
| Replace a signed mifest and use an unrelated signing key | Do not report trusted authenticity. |
These are proposed acceptance tests, not benchmark results. Keep fixtures for older supported format versions so the next exporter release does not quietly strand yesterday’s files.
Offline readability is also different from restoration. Importing the package back into a working application requires separate tests for permissions, identifiers, conflicts, and derived state. Call the package a backup only if its recovery contract is defined and tested.
The handoff is part of the product
Antler Labs’ public project page describes FileOnChain as an in-development effort around portable evidence and independent verification. As of October 15, 2026, that is a stated project direction, not a claim here that the export design above is implemented.
The broader engineering principle applies to ordinary workspaces, datasets, and document tools too: the useful life of someone’s work should not end at ygin screen.
Start with a small, documented package. Include the bytes it promises. Expose the limits of its checks. Then turn off the network and see what remains useful.
That is a stronger acceptance test than watching a download reach 100%.
Part of the Antler Labs blog. Previous: Before your AI agent retries, give the action an identity.
Designing a product whose data needs to outlive its hosting? Talk to Antler Labs.