# Part XV. Assets

*This part is a part of OpenEik 0.2. The steward asked for it on 18 October 2026: Part XIV keeps fonts, PDF renditions, outside drawings and signed containers as binary files by digest, and no version of 0.1 can hold one. It binds every conformance class it names (section 11; [Conformance](https://openeik.org/spec/0.4/conformance)). OpenEik 0.1 had it as a proposal, which no class of 0.1 required: a reader of 0.1 leaves binary files alone, `assets/` among them, and never removes them (Part III, 7, rule 2). The steward decided its six questions on 18 October 2026, as the proposal had them (open questions 30 to 35; section 12). Since 23 October 2026, `@openeik/tools` keeps assets in its versions, for every model of 0.2, and for a model of 0.1 with the proposal `assets` (section 11).*

## 1. Status and scope

An **asset** is a binary file of a model, kept by the SHA-256 of its bytes: a PDF rendition or an outside drawing, a font, a signed container, a photograph attached to a review. Part XIV names its assets at `assets/<sha256>.<ext>` (Part XIV, 7.3). A later version may add other kinds, such as the large meshes of declared geometry (Part II, 6.6, rule 6).

A version of 0.1 holds records and kept files, each as the canonical form of JSON text (Part VI, 1), and a kept file is UTF-8 text of at most 1 MiB (Part III, 7, rule 2). A binary file is left alone: no version holds it, no store keeps it, and a push, a pull or a clone leaves it behind. So a drawing issued with its fonts and its PDF/A rendition reaches another store without them, and its sign-offs name a file that no one there can fetch.

This part keeps an asset in a version as Git LFS keeps a large file in a git repository: by its digest. In a folder, an asset is the file itself. In a version's tree, it is a small **pointer** that names the bytes by their SHA-256 and their length. The bytes are kept beside the versions, in the store, by their digest, and travel with a push, a pull and a clone. Since a version names an asset's bytes by their digest, its root hash commits to them (section 3); and since the bytes are not in the version, a store can let go of bytes that have to go without rewriting any version (section 7).

### 1.1 The parts it extends

In 0.2 its text stays in this part. These are the sections of Parts I to VIII and of the other parts that it extends, where a later version may move it:

- Part III: `assets/` in the folder (1), assets as a file kind of their own beside records and kept files (3 and 7), and what the single-file form leaves out (11, rule 3);
- Part VI: pointers in trees (2), assets in a diff and a merge (4 and 5), and in a fetch, a push, a pull and a clone (6);
- Part XIV: 7.3 and 7.4, rule 5 point to this part, whose kinds add the photographs that 13.2 attaches;
- Conformance: what a reader, a writer, a validator and a version store do with assets;
- Security and privacy: assets in 2.4, 5.3, 7.1 and 11;
- the extension registry: `assets` becomes a folder the standard gives, no longer one it plans.

## 2. In a folder

1. An asset is the file `assets/<sha256>.<ext>`: `<sha256>` is the SHA-256 of the file's bytes, as 64 lowercase hexadecimal digits, and `<ext>` the extension of its kind (section 4). `assets/` holds assets only, and no folder.
2. An asset is never changed: its name is its bytes' digest. A writer MUST write an asset's bytes as they are, and MUST NOT write over an asset.
3. A file in `assets/` whose bytes do not give its name, or whose name is not an asset's, is not an asset. A reader MUST NOT read it as one: it reports the file and leaves it alone. A validator reports it as an error.
4. A writer MUST NOT remove an asset that the model names (section 8). It MAY remove one that the model does not name.
5. An asset is not a kept file: Part III, 7's limits of size and of text do not hold for it, and section 4's do.

## 3. In a version

1. In a version's tree, an asset is the leaf at its path in the folder, `assets/<sha256>.<ext>`, extension included. Its object is the asset's **pointer**: the canonical form (Part III, 6) of `{"asset": {"bytes": <n>, "sha256": "<sha256>"}}`, where `bytes` is the file's length in bytes and `sha256` its name's digest.
2. A reader of versions MUST refuse a version that holds, under `assets`, a sub-tree that is not a shard (Part VI, 2.3), or a leaf that is not such a pointer or whose `sha256` is not its name's digest, naming the entry.
3. The bytes are not in the tree. A version store keeps them beside its objects, by their SHA-256 (section 5).
4. A version's root hash commits to every asset the version holds: two versions with one root hash name the same bytes, by the same digests.
5. Making a version from a folder makes each asset's pointer, and writing a version into a folder writes each asset's bytes at its path. Neither changes a byte.
6. A diff reports an asset added or removed, by its path (Part VI, 4). An asset is never changed: other bytes are another asset, at another path.
7. A merge merges pointers file by file, as it merges kept files (Part VI, 5, rule 5). An asset added on both sides is one asset. A merge that removes an asset which the merged model names MUST be refused, naming the asset and what names it (Part VI, 5, rule 7).

A folder holds a PDF rendition of 812 344 bytes at `assets/<sha256>.pdf`. The version's tree holds, in its sub-tree `assets`, the entry `["<sha256>.pdf", "b", "<the pointer's hash>"]`, and the pointer is

```json
{"asset":{"bytes":812344,"sha256":"<sha256>"}}
```

*Note (informative).* Why a pointer, and not the bytes as an object of the tree. Every reader and store of 0.1 holds objects as JSON text, checks each object's hash against that text (Security and privacy, 5.3) and walks trees of two kinds of entry. A pointer is one more leaf of JSON text: trees, diffs, merges and a store's objects work as they do, and only a reader or writer of folders, a fetch and a push handle bytes. That is how Git LFS fits large files into git, and its pointer names the same SHA-256. It is also why a store can let bytes go without breaking a version (section 7). The other way, a third kind of tree entry that names the bytes themselves, needs no pointer, but every tree reader, every store of objects and every host would change with it (open question 30).

## 4. Kinds and limits

| Extension | Media type | What | Its bytes begin with |
|---|---|---|---|
| `pdf` | `application/pdf` | a PDF rendition, an outside drawing (Part XIV, 8.3 and 15) | `%PDF-` |
| `ttf` | `font/ttf` | a TrueType font of the drawing profile (Part XIV, 4.1) | `00 01 00 00` or `true` |
| `otf` | `font/otf` | an OpenType font with CFF outlines | `OTTO` |
| `asice` | `application/vnd.etsi.asic-e+zip` | a signed container of the legal tier (Part XIV, 14.6) | `PK 03 04`, its first entry `mimetype` |
| `png` | `image/png` | an image attached to a review item (Part XIV, 13.2) | `89 50 4E 47 0D 0A 1A 0A` |
| `jpg` | `image/jpeg` | a photograph attached to a review item | `FF D8 FF` |

1. An asset MUST be of one of these kinds. Its bytes SHOULD begin as its kind's do; a validator warns about one that does not.
2. An asset MUST be at most 64 MiB (67 108 864 bytes), as an issued drawing is (Part XIV, 7.3). A host MAY set a lower limit, and a quota for a building, and says what they are.
3. A later version may add kinds. A reader keeps an asset of a kind it does not know: in a folder it leaves the file alone, and in a version it keeps the pointer and writes it back. A writer of 0.2 MUST NOT write one.

*Note (informative).* Large meshes, for declared geometry of more than 10 000 faces (Part II, 6.6, rule 6), are a kind for later: they need a format (glTF's binary form, `glb`, is the likely one), their frame and their checks first.

## 5. In a store

1. A version store keeps the bytes of assets by their SHA-256, beside its objects. When it takes bytes in, it MUST check that their SHA-256 and their length are those of the pointer that names them, and refuse them otherwise.
2. A store that others fetch from (a host) MUST hold the bytes of every asset that a version on one of its refs names, unless it has withheld the asset (section 7). It MUST NOT move a ref to a version that names an asset whose bytes it neither holds nor has withheld.
3. A store MAY hold a version without its assets' bytes, and fetch them when they are needed: a partial store, as a clone with Git LFS is. Asked for an asset it lacks, it MUST say that it lacks it, and never answer with other bytes.
4. A store answers for an asset, with its bytes or with whether it holds them, within one building only: to a caller who may read the building, about the assets its versions name, and to a caller who may push to it, about the assets a push sends. An answer for a digest says that the file exists: a store that keeps one copy of some bytes for several buildings MUST NOT let its answer for one building tell of another's.

## 6. Push, fetch, pull and clone

1. **A push** sends, before it moves the remote ref (Part VI, 6.3), the bytes of every asset that a version it sends names and that the remote lacks. Only bytes the remote lacks are sent. A squashed push (Part VI, 6.3, rule 3) sends one version, and so the assets of the newest tree only.
2. **A fetch** reads the objects it needs, pointers among them (Part VI, 6.2). It MAY leave the bytes behind.
3. **A pull or a clone into a folder** MUST fetch the bytes of every asset of the version it writes, and check each as section 5, rule 1 says, before it writes anything. An asset that it cannot fetch, or that fails the check, stops it, naming the asset, unless the asset is withheld (section 7).
4. A store that pulls or clones without writing a folder MAY fetch the bytes later, when an asset is read.

## 7. Withheld assets

1. A store MAY **withhold** an asset: let go of its bytes, keep every pointer to it, and record that it is withheld. It does so when the asset has to go: personal data that a person asks to have erased (Security and privacy, 2.4), a licence that has ended, an order. A withheld asset stays withheld in that store.
2. A version that names a withheld asset stays readable. A reader reports the asset as withheld, a warning; a checkout writes no file for it; and a validator does not count it as missing (section 8, rule 2).
3. A push of a version that names an asset withheld at the pushing store says so. The remote MAY then accept the version and record the asset as withheld, or refuse it.
4. What was signed by the asset's digest stays signed (Part XIV, 14), and can no longer be checked against its file: a reader says so.

*Note (informative).* A value in a record stays in every version that holds it, and a store can take it out only by dropping versions (Security and privacy, 2.4). An asset's bytes are kept beside the versions, so a store can let them go and keep every version whole: the photograph of a person attached to a review can be erased, and the review, its versions and its sign-offs remain. A store that has withheld an asset still answers for its digest that it is withheld, so that readers can tell withheld from lost.

## 8. References

1. Part XIV names assets by path, `assets/<sha256>.<ext>`: an unlinked drawing's `file.asset` (8.1 and 8.3), the drawing profile's `fonts[].file` (4.1), a review item's `attachments[].asset` (13.2), and a sign-off's `legal.asset` and its statement's subject (14.1, 14.2 and 14.6). Its register names them by digest (11.1). A `sha256` given beside a path MUST be the path's digest.
2. In a model, an asset named by path MUST be in it: in a folder, its file; in a version, its pointer. A validator reports one that is not as an error, and one that is withheld as a warning.
3. An asset that no member of the model names is **unused**. A validator MAY warn about it, and a writer MAY remove it (section 2, rule 4); the versions before keep it.
4. A digest without a path names a file the model need not hold: a font that the project may not store (Part XIV, 4.3, rule 4), or a rendition that was not kept (Part XIV, 15.2, rule 5).

## 9. The single-file form, git and hosts

1. The single-file form (Part III, 11) carries no asset. Its members name assets by path, and the assets travel beside it, in a folder `assets/` next to the file. Reading a folder into one file and writing it back loses no asset only when the assets travel so; Part III, 11, rule 3 counts them among what the single-file form may lose.
2. A folder kept in git keeps its assets as files. A tool SHOULD mark `assets/**` as binary (`-text`), as Part XIV, 1.5 says, and MAY keep them with Git LFS, whose pointer names the same SHA-256 as this part's.
3. A host whose store takes only JSON text keeps no asset until it keeps bytes beside its objects. Until then it MUST NOT move a ref to a version that names an asset (section 5, rule 2).

## 10. Security and privacy

1. **No asset is code** (Security and privacy, 11). A reader that shows an asset MUST NOT run anything it holds: a PDF's scripts, actions and launch links, or a program in a container. It reads a rendition's attachments as data, to check them against the issued files (Part XIV, 15).
2. **Fonts.** A reader that draws with a font from a model SHOULD pass it through a font sanitiser first, as browsers do with fonts from the web: font files have carried attacks on the programs that draw them.
3. **Containers.** A reader that opens a container MUST bound the number and the size of its entries, and refuse an entry whose path leaves the container.
4. **Bounds.** A reader of a model from a source it does not control SHOULD bound how many assets, and how many bytes in all, it fetches and keeps (Security and privacy, 7.2).
5. **Integrity.** An asset is checked by its SHA-256 wherever it arrives (section 5, rule 1). A store that answers other bytes for a digest is refused (section 6, rule 3).
6. **Photographs are personal data.** A photograph shows people, places and things, and its metadata may say where and when it was taken, and with which camera. A writer SHOULD take that metadata out of a photograph before it becomes an asset: once named by its digest, its bytes are fixed. A store withholds a photograph that a person asks to have erased (section 7).
7. **Confidential files.** An outside drawing or a rendition is as confidential as the model it is in. A host gives an asset only to those who may read a version that names it (section 5, rule 4). A host that narrows what a member reads by folder treats `assets/` as a folder like any other.
8. **Serving a file.** A host that serves an asset over the web SHOULD send it with its kind's media type and as a download, never as a page of its own site: a file that a browser takes for a page could run there.

## 11. Conformance

A validator of 0.2 checks section 2's names against the bytes, section 4's kinds and limits, section 3's pointers in a version, and section 8's references. A version store of 0.2 keeps bytes as section 5 says, and moves them as section 6 says.

*Note (informative).* What implementations do on 23 October 2026. `@openeik/tools`, for every model of 0.2, and for a model of 0.1 asked for the proposal `assets`:
- checks a folder's assets as sections 2 and 4 say;
- makes each asset's pointer in a version, and reads and checks the pointers of a version it reads (section 3);
- sends the bytes a store lacks before a push moves its ref, and fetches and checks every asset of the version a clone or a pull writes before it writes anything (section 6);
- merges a pull's assets file by file, refusing a merge that removes an asset the merged model names (section 3, rule 7);
- keeps the pointers of assets a store has withheld (section 7).

It talks to a store through the three `assets` calls of the store API it speaks, which the guide *Reading and saving models* describes (section 5.2), and has 8 conformance cases for a folder's rules, in the suite of 0.2 (`conformance/`). Since 30 October 2026 it keeps an asset of a kind it does not know as section 4, rule 3 says: a clone or a pull keeps its pointer and writes no file of it, and the folder's next push writes the pointer back unchanged.

The reference implementation keeps the PDFs it publishes as files of its project, outside the model, and its issued SVGs as kept files (Part XIV, 1.5). A store that takes objects as JSON text, and checks each object's hash against its text, cannot hold an asset as an object.

## 12. The decisions

The steward decided open questions 30 to 35 on 18 October 2026, each as this part proposed it.

| Question | Decision | Decided |
|---|---|---|
| 30 | How a version holds an asset | a pointer leaf of JSON text, with the bytes beside the version (section 3) |
| 31 | The kinds of 0.2 | `pdf`, `ttf`, `otf`, `asice`, `png` and `jpg`; meshes later (section 4) |
| 32 | The size of an asset | at most 64 MiB, and a host's own limit and quota below it (section 4) |
| 33 | Whether a fetch may leave bytes behind | yes; a pull or a clone into a folder fetches what it writes (section 6) |
| 34 | Whether a store may withhold an asset | yes, keeping every pointer to it (section 7) |
| 35 | Who may remove an unused asset | any writer; the versions before keep it (sections 2 and 8) |
