Designing MeshPack: A Case Study in Interoperable 3D Asset Boundaries
What a MeshSync-maintained 2.0 release-candidate proposal reveals about designing portable asset inventories without overstating their boundary.
Interoperability formats repeatedly encounter two risks: abstraction so clean that it misses real work, and coupling so close to one implementation that other systems inherit its accidents. Neither risk explains every failed standard, but 3D asset workflows make both unusually visible.
One “model” can be an STL, an OBJ and material file, a 3MF archive, textures, a preview, a licence, a README, and variants that only make sense together. It may sit in a local folder, NAS, object store, cloud drive, or marketplace export. A boundary for that world must preserve useful facts without smuggling storage-provider behaviour into every downstream worker.
MeshPack is a MeshSync-maintained format proposal for that boundary, not an adopted standard. Its public VERSION declares 2.0.0, while its specification metadata describes a release candidate; the public releases and tags pages currently expose no release or tag. This article therefore treats MeshPack 2.0 as a design case study: a proposed portable, verifiable inventory shaped by awkward workflow pressures.
The practical payoff for an architect is not “use this everywhere”. It is a way to make format choices traceable: state the pressure, choose a mechanism, name its cost, and define the condition under which it must be revisited. For the product-level context, see the Mesh-Sync strategic architecture post.
flowchart LR
S[Provider-specific scan] --> P[MeshPack snapshot<br/>metadata + optional small resources]
P --> M[Metadata and structure workers]
P --> R{Embedded bytes available?}
R -->|Yes| G[Thumbnail or geometry worker]
R -->|No| C[Separate content-access contract]
C --> G
G --> D[Derived output or external enrichment]
The pack is sufficient for inventory work. It is not, by itself, a universal hand-off for workers that need source geometry or thumbnails.
Start with the boundary, not the container
MeshPack is a ZIP-based snapshot of metadata and structure, with optional small embedded resources. It normally does not carry the source geometry of every indexed file. That distinction is the central design constraint.
A metadata, classification, or structure worker can operate on the pack’s manifest and index. A thumbnail or geometry worker needs either embedded bytes or a separate, provider-neutral content-access contract. A path and a hash identify a claimed object; neither retrieves it. That adjacent contract would need to address content identity, authorisation, expected size and hash checks, retry behaviour, and expiry. It should not put provider credentials or transient signed URLs into a portable pack.
This makes “provider-agnostic” deliberately bounded. The inventory can cross providers; fetching absent source bytes cannot. Offline processing is consequently limited to the metadata and embedded resources available in the archive. That is an honest boundary rather than a gap a reader should silently fill.
The proposal definition describes a compact core around manifest.json and index shards, with optional resources and an optional mapping.db cache. The following is a conceptual layout, not a claim that an archive contains a literal root/ directory:
manifest.json
_README.md
index/
_README.md
part-00001.json
part-00002.json
resources/ # optional small embedded resources
<content-hash>.jpg
mapping.db # optional acceleration cache
mapping.db is not authoritative index data. A reader must treat it as untrusted parser input and must not let it override the manifest or shards. It is included in the final archive bytes for archive-integrity purposes; its possible nondeterminism matters instead to reproducible package production.
Make the trade-offs explicit
| Workflow pressure | Proposed choice | Alternative not chosen | Cost introduced | Revisit when |
|---|---|---|---|---|
| Users need one inspectable hand-off | ZIP archive with a custom extension | Directory-plus-manifest or a database package | ZIP parsing and extraction hazards; weak remote random access | Remote access to heavy content dominates |
| Large inventories need bounded units | Sharded JSON index | One monolithic JSON file, SQLite, or a content-addressed index | Cross-shard validation and ordering rules | Query patterns require transactional or indexed state |
| The archive needs an exact-byte digest | Detached integrity sidecar | An embedded self-reference | Sidecar distribution and trust become separate problems | A signed distribution envelope supplies a better trust boundary |
| Most workers need structure, not bytes | Metadata inventory plus optional small resources | Self-contained payload bundle | Geometry workers require a separate retrieval capability | Offline geometry processing is the primary workload |
This table also marks poor fits. MeshPack is not a good primary model for high-frequency multi-writer transactional state, confidential metadata without a separate protection layer, remote random access to heavy geometry, or full semantic 3D interchange. A container can be a useful boundary without being a synchronisation database, a secure vault, or a geometry interchange language.
Inventory mechanics: useful, but not magical
The manifest provides package context and a list of shards. The current manifest schema requires, among other fields, creator_info and index_summary.total_size_bytes. manifest.json being first in central-directory order provides predictable lookup order. It does not, on its own, make the package one-pass or non-seekable-stream friendly.
// Abbreviated, non-valid excerpt: hashes are deliberately truncated.
{
"format_version": "2.0.0",
"created_at": "2026-01-06T12:00:00Z",
"creator_info": { "name": "example-scanner" },
"workspace_id": "550e8400-e29b-41d4-a716-446655440000",
"index_summary": {
"total_files": 15420,
"total_shards": 2,
"total_size_bytes": 8042312241
},
"hash_algo": "sha256",
"shard_list": [
{ "id": "part-00001", "entries_count": 10000, "entries_hash": "sha256:…" },
{ "id": "part-00002", "entries_count": 5420, "entries_hash": "sha256:…" }
]
}
The shard schema defines entries such as paths, content hashes and modified_at. Proposal limits such as 10,000 entries per shard and the advisory 5 MB shard scale are useful operational constraints, not benchmark evidence or a guarantee of predictable total memory use. A consumer still needs its own limits for parsed JSON, metadata size, recursion, and work queues.
Sorted entries and JCS/RFC 8785 can stabilise shard hashing. They do not make whole ZIP archives byte-identical. JCS operates on a JSON value only after its I-JSON constraints are satisfied: duplicate object names, non-interoperable numbers, malformed Unicode, and Unicode handling remain implementation concerns. For the exact value and fields covered by a proposal hash, an implementation should follow the normative MeshPack definition rather than infer scope from a convenient serializer.
If byte-reproducible packages matter, a producer should omit nondeterministic cache bytes and also control ZIP timestamps, ordering, compression settings, attributes, and library behaviour. Exact-byte archive integrity and reproducible production are related but different requirements.
Exact bytes, trusted expectations, and safe handling
The L2 archive-hashing requirements use a detached .meshpack.integrity sidecar. Its pack hash uses the manifest-selected hash_algo (with SHA-256 recommended) over every byte of the final .meshpack archive, including mapping.db when present. There are no ZIP-entry exclusions. A detached value is necessary because an archive cannot contain an authoritative hash of its own final bytes without changing those bytes.
flowchart TD
T[Independently trusted expected digest<br/>or trusted verification key] --> S[Read sidecar or signature material]
A[library.meshpack] --> H[Hash every final archive byte<br/>with manifest-selected hash_algo]
S --> C{Expected digest available and valid?}
H --> M{Digest matches?}
C -->|No| X[No authenticated acceptance]
C -->|Yes| M
M -->|No| R[Reject]
M -->|Yes| Z[Parse ZIP defensively, then validate manifest and shards]
Z --> O[Produce immutable derived output or external enrichment]
The flow separates three properties that are often collapsed:
- Integrity: a matching digest detects alteration only relative to a trusted expected digest.
- Authenticity: a signature helps only when its verification key is independently trusted. NIST SP 800-89 discusses assurance requirements for digital signatures and public keys. An archive and unsigned sidecar delivered by the same untrusted channel do not authenticate a source.
- Confidentiality: neither hashes nor signatures hide metadata or resources. Confidential packages need a separate protection and key-management layer.
The sidecar can state size as a cheap consistency check, but the archive digest is the exact-byte check. Signatures and key distribution or revocation policy should not be assumed merely because a format has a signature field; those policies need explicit specification and operational ownership.
Verified input also should not be mutated in place to attach worker results: mutation invalidates its digest and any signature over it. A processing architecture therefore needs immutable input plus derived output, or external enrichment. If several workers can enrich the same inventory, lineage, provenance, conflict ownership, and merge policy are requirements to resolve~not lifecycle behaviour established merely by the container.
ZIP validation is a separate security boundary
FileEntry.path validation is semantic validation for the scanned inventory: forward slashes, relative paths, no traversal or drive-root forms, and any format length rules. It is not ZIP-member validation. Before extraction or trusting member contents, a reader must treat the archive itself as untrusted input.
At minimum, defensive ZIP handling needs a member-name and target-filesystem preflight for duplicate and case-colliding names, absolute or traversal names, links and other special files, and destination collisions. Implementations should also consider disagreement between local headers and the central directory, and enforce compressed-size, expanded-size, member-count, path-depth, and processing-time limits. ZIP’s format details are defined in APPNOTE; common extraction APIs and their decompression pitfalls do not remove the need for resource controls. Traversal remains a well-known weakness (CWE-22).
Index-path validation alone does not secure extraction. Nor does a valid hash make an archive safe to parse, or secret to disclose.
Conformance should name roles, not imply adoption
MeshPack describes L1, L2, and L3 proposal tiers, but they mix roles and capabilities. They are a way to stage an implementation plan, not proof that independent implementations conform. Practical claims should instead be specific: reader, writer, schema validator, integrity verifier, delta processor, or extension processor.
The validation contract distinguishes compatibility validation, which warns on unknown core properties, from strict validation, which rejects them. This is useful for migration, but it means a consumer must select its mode deliberately. Project SemVer policy similarly communicates intent; SemVer alone cannot guarantee that a strict reader, a newly introduced enum value, or generated types remain compatible in every consumer.
The public repository includes schemas, validator behaviour, and selected requirement coverage (coverage notes). That is stronger than prose alone, but some ecosystem and large-scale obligations remain manual or the responsibility of consumers. A validator, generated model, or RFC process is evidence of project discipline, not evidence of interoperability or adoption.
Identity, extensions, and deltas keep their limits
Namespaced identifiers preserve both an identifier and its context. They do not establish that two records are equivalent, and a content hash establishes byte equality rather than logical asset identity. Reconciliation and precedence remain importer policy: one importer may favour a local authoritative ID, another an external listing ID, and both need auditable rules.
Extensions provide a versioned, namespaced place for metadata outside the core. A reader can have an unknown-version policy, but the presence of v1 and v2 values does not by itself prevent disagreement between them. Schema identity, provenance, coordinate system, units, and measurement method matter as much as a canonical-looking field name; volume_mm3 is not meaningful if producers derived it differently.
Delta packs are best read as a single-known-base patch/state-model sketch, not a complete synchronisation protocol. A consumer must verify base_pack_hash before application. Duplicate operations, operation preconditions, conflict handling, resources, atomic failure, and verification of the resulting output are still consumer or future-specification responsibilities. Path-map deletes and upserts are not sufficient for concurrent or bidirectional sync.
flowchart LR
B[Verified base pack] --> V{base_pack_hash matches?}
D[Delta pack] --> V
V -->|No| R[Reject or resolve outside this model]
V -->|Yes| A[Apply defined operations to a derived state]
A --> O[Validate and verify derived output]
What would make stronger claims warranted?
MeshPack is project-governed and maintained by MeshSync, not presented here as neutral multi-party governance. Its public governance document, normative artefacts, tests, and release practices are useful things to inspect separately; they do not turn a release candidate into a broadly adopted standard.
Stronger language would need public change control, stable releases, independent producers and consumers, and demonstrated interoperability. Until then, the valuable lesson is more modest. Start with the pressures that make a boundary necessary; separate an inventory from access to its content; make byte integrity, authenticity, and confidentiality distinct; and state where the proposal stops.
That discipline is what makes a format design useful before it is universal. The worker side of the wider architecture is covered in the contract-first worker platform post.