# Part III. Code base

An OpenEik model is a folder of files: one record per file, at a path derived from the record, in a fixed text form. A folder and a stored version are the same format (Part VI), and the folder works with git, editors and an agent's file tools as a code base does.

## 1. The folder

```text
eik.json                                       format and units
AGENTS.md                                      conventions for agents (text)
project/project.json                           the project
project/levels.json                            the levels, in order
project/order.json                             the order of the record lists
project/materials/<id>.json                    and grids, scope-boxes, dimension-styles,
                                               detail-families, title-blocks, images,
                                               originals, copies
project/circuits/<id>.json                     circuits (Part XIII), and project/people/<id>.json
                                               the people of the registry (Part XIV)
types/<discipline>/<category>/<id>.json        types
<discipline>/<level>/<category>/<id>.json      elements: architecture, structure, ventilation,
                                               plumbing, electrical, sprinkler, site
drawings/<discipline>/views/<kind>/<id>/view.json
                                               a view
drawings/<discipline>/views/<kind>/<id>/details/<n>.json
                                               its detail items, in chunks
drawings/<discipline>/sheets/<id>.json         sheets (Part XIV)
documents/<list>/<id>.json                     reports, renders, deviations,
                                               area-adjustments, references (application records)
fabrication/pieces/<id>.json                   pieces and joints (fabrication profile)
fabrication/joints/<id>.json
<prefix>/…                                     an application's own files (section 7.7; Part I, 11.3)
anything else                                  kept files (section 7)
.eik/, .git/, …                                hidden: not part of the model
```

`examples/one-storey-house` is a complete folder of 34 files.

This is the owners' layout of 0.2: [section 4.4](https://openeik.org/spec/0.2/code-base#4.4) puts each element, type, view and sheet in the folder of the discipline that owns it, one of eight. A folder of 0.1 files them by category in four folders, `architecture`, `structure`, `systems` and `site`, its views under `drawings/views/` and its sheets, application records of 0.1, under `drawings/sheets/` (section 4.1). A reader of 0.2 reads either layout (section 4.4, rule 7).

1. A file or folder whose name starts with `.` is hidden. It is not part of the model; readers MUST skip it and writers MUST NOT remove it. Two hidden paths have a defined use: `.eik/derived/`, the optional derived layer of Part IV, section 1.1, and `.eik/remotes.json`, a folder's remote-tracking refs (Part VI, 6.1).
2. A name that starts with `~` is reserved (Part VI uses it for shard folders). Writers MUST NOT create one in a folder.

Reference: `readFolder` in `server/eikSession.ts` (skips names starting with a dot); `keptPath` in `src/core/model/extras.ts`.

## 2. Path segments

Some names in a record's path are made from ids: an element's level folder from its `levelId`, a view's folder from the view's id, and every other record's file from its id, followed by `.json` (sections 3 and 4.2). Each is the id as a **path segment**: the id with some of its characters written as their **escape**, `%` followed by the two hexadecimal digits of the character's code point, so that every id gives one name that common file systems take and read as it is written. Rules 1 to 6 make an id's segment, in this order, each rule applied to what the rules before it give:

1. Every `%` is escaped as `%25`, first: the `%` of an escape is never escaped again.
2. Every character that a common file system refuses in a name is escaped: `/` as `%2F`, `\` as `%5C`, `:` as `%3A`, `*` as `%2A`, `?` as `%3F`, `"` as `%22`, `<` as `%3C`, `>` as `%3E`, `|` as `%7C`, and each control character, U+0000 to U+001F, as `%00` to `%1F`.
3. A leading `.` is escaped as `%2E`, and a leading `~` as `%7E`. Readers skip a hidden name and writers make no reserved one (section 1), so a record with the id `.x` would otherwise be lost.
4. A trailing `.` is escaped as `%2E`, and a trailing space as `%20`: some file systems drop them.
5. The first character of a segment that is a device name of a common operating system is escaped. What is compared is the segment's part before its first `.` (the whole segment where it has none), without the spaces that end that part; it is a device name when it is `CON`, `PRN`, `AUX`, `NUL`, `COM0` to `COM9`, `LPT0` to `LPT9`, or `COM` or `LPT` followed by `¹`, `²` or `³`, compared without regard to ASCII case. A record's file, its segment followed by `.json`, has the same part before its first `.`, so one comparison serves the segment and the file, and a device name is escaped with or without an extension: the ids `con`, `Aux.1` and `lpt¹` make the files `%63on.json`, `%41ux.1.json` and `%6Cpt¹.json`.
6. The hexadecimal digits of an escape are upper case.
7. A reader reads a segment back by decoding, in one pass from left to right, every `%` followed by two hexadecimal digits, of either case, as the character of that code point (U+0000 to U+00FF). Any other `%` stands for itself.
8. A writer MUST write each name made from an id as the id's segment. A reader MUST read each such name by rule 7.
9. A name that reads by rule 7 as its record's id, or as an element's `levelId`, but is not its segment, as a name written before these rules with a `:` in it, is at the record's path in an older form, and does not disagree with the record (section 4.2). A reader SHOULD warn about such a name, naming the path these rules give. A writer writes the record at that path, and removes the file it was read from (section 13, rule 4). A name that starts with `.` or `~` is hidden or reserved (section 1), and is never read as a record, whatever it decodes to.

A valid id holds no `/` (Part I, 6.4). Rule 2 escapes it all the same, so that every string has exactly one segment, and so that a reader can read a folder that breaks the rule and name the record it refuses. A category and a view's kind are names of the format, and are not escaped: a category is a plain name (Part II, 6.2, rule 1), and a view's kind one of the five its schema lists. Nor is a kept file's path escaped, which is its own: it holds no name that rules 2 to 5 would escape (section 7, rule 8). Part I, 6, rule 8 bounds a segment's length, and section 4.3 says when two names fold to one, which Part I, 6, rule 7 keeps two records' paths from doing.

*Note (informative).* These rules were decided by the steward on 14 October 2026. Before them, this section escaped only `%`, `/` and a leading `~`, so a folder written earlier may hold, as it is, the `:` of a derived id (Part I, 6, rule 6) in a file's name, which Windows cannot check out; rule 7 reads the name as the same id, and rule 9 has it reported. An escape is the percent-encoding of URIs (RFC 3986, 2.1) of an ASCII character, and a writer escapes no other character; rule 7 decodes a character, not an octet of UTF-8, so `%C3%A9` reads as `Ã©`, never as `é`. The names of rule 5 are those that current Windows documentation reserves ([Naming files, paths, and namespaces](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)), `COM0`, `LPT0` and the superscript digits among them; Windows reads `NUL.txt` and `NUL.tar.gz` as the device, and `NUL .txt` too.

Reference: `segment` and `unsegment` in `src/core/versions/layout.ts`, rules 1 to 7, and `manifestOfFolder` in `src/core/versions/eikFolder.ts`, rule 9, since Stomra 506 (live since 15 October 2026, `main` `a34cc1a1`); before it they escaped and decoded only `%`, `/` and a leading `~`.

## 3. File kinds and their schemas

| Path | Holds | Schema |
|---|---|---|
| `eik.json` | `{format, units}` | `eik.schema.json` |
| `AGENTS.md` | text | none |
| `project/project.json` | `{project, projectInformation?, schemaVersion?}` (`schemaVersion` deprecated in 0.2, Part I, 9.3) | `project/project.schema.json` |
| `project/levels.json` | the list of levels | `project/levels.schema.json` |
| `project/order.json` | list name → ids | `project/order.schema.json` |
| `project/grids/<id>.json` | a grid | `project/grid.schema.json` |
| `project/scope-boxes/<id>.json` | a scope box | `project/scope-box.schema.json` |
| `project/materials/<id>.json` | a material | `project/material.schema.json` |
| `project/dimension-styles/<id>.json` | a dimension style | `project/dimension-style.schema.json` |
| `project/detail-families/<id>.json` | a detail family | `project/detail-family.schema.json` |
| `project/title-blocks/<id>.json`, `project/images/<id>.json` | application records | `application-record.schema.json` |
| `project/originals/<id>.json`, `project/copies/<id>.json` | an original, a copy | `project/original.schema.json`, `project/copy.schema.json` |
| `project/circuits/<id>.json` | a circuit ([Part XIII, 3](https://openeik.org/spec/0.2/electrical)) | `project/circuit.schema.json` |
| `project/people/<id>.json` | a person or an agent of the registry ([Part XIV, 5](https://openeik.org/spec/0.2/drawings)) | `project/person.schema.json` |
| `types/<discipline>/<category>/<id>.json` | a type | `types/<category>.schema.json` |
| `<discipline>/<level>/<category>/<id>.json` | an element | `elements/<category>.schema.json` |
| `drawings/<discipline>/views/<kind>/<id>/view.json` | a view without its detail items | `drawings/view-record.schema.json` |
| `drawings/<discipline>/views/<kind>/<id>/details/<n>.json` | a chunk of detail items | `drawings/view-details.schema.json` |
| `drawings/<discipline>/sheets/<id>.json` | a sheet ([Part XIV, 2](https://openeik.org/spec/0.2/drawings#2)) | `drawings/sheet.schema.json` |
| `documents/reports/<id>.json`, `documents/renders/<id>.json`, `documents/deviations/<id>.json`, `documents/area-adjustments/<id>.json`, `documents/references/<id>.json` | application records | `application-record.schema.json` |
| `fabrication/pieces/<id>.json`, `fabrication/joints/<id>.json` | a piece, a joint | `fabrication/piece.schema.json`, `fabrication/joint.schema.json` |

These are the paths of the owners' layout of 0.2 (section 4.4): `<discipline>` is the record's owner, one of eight, and a view and a sheet are under their owner's folder. In 0.1's layout (section 4.1), `<discipline>` is one of four, a view is at `drawings/views/<kind>/<id>/view.json`, its chunks beside it, and a sheet, an application record of 0.1, at `drawings/sheets/<id>.json`, of the same depths below `drawings/`. The schemas are those of 0.2, in `schemas/0.2/`; 0.1's are in `schemas/`.

[Part XIII](https://openeik.org/spec/0.2/electrical) adds the electrical records of 0.2, which are not 0.1's: the elements and types of the categories `electrical`, `cableCarrier` and `cable`, and the circuits at `project/circuits/<id>.json`. [Part XIV](https://openeik.org/spec/0.2/drawings) adds the drawing files of 0.2, which are not 0.1's either: sheets as standard records, people and agents at `project/people/<id>.json`, the drawing profile, issued drawings, registers, issues and reviews under `drawings/`, and assets by digest at `assets/<sha256>.<ext>`. [Part XV](https://openeik.org/spec/0.2/assets) says how a version and a store keep those assets: a pointer in the version's tree, and the bytes beside it. [Part XVI](https://openeik.org/spec/0.2/ownership) adds `project/owners.json` (`project/owners.schema.json`), and [Part XVIII](https://openeik.org/spec/0.2/review) a model's reviews and terms accepted.

1. A path is a record's path only when it has exactly the depth shown: an element is three segments under a discipline folder (its level, its category and its file), a type three under `types` (its discipline, its category and its file), a list record one segment under its list folder. Any other file is a kept file (section 7), even if it ends in `.json`.
2. Every record file MUST validate against its schema, that of its model's version. [`schemas/0.2/index.json`](https://openeik.org/schemas/0.2/index.json) lists the same mapping for tools, and [`schemas/index.json`](https://openeik.org/schemas/0.1/index.json) 0.1's. An element or a type of a category that has no schema of its own here, one a later version adds, validates against `elements/element.schema.json` or `types/type.schema.json` (Part I, 10, rule 5).

Reference: `leafPlace` in `src/core/versions/tree.ts`; `recordFile` in `src/core/versions/eikFolder.ts`.

## 4. Where a record goes

A record's path is derived from the record. Its identity is its id, never its path: an element moved to another level is the same element in a new file.

### 4.1 Disciplines in 0.1

This section is 0.1's layout, which a writer of 0.1 writes and a reader of 0.2 reads (section 4.4, rule 7). A writer of 0.2 writes the owners' layout of section 4.4, whose rules replace this table and rules 1 and 2.

An element's discipline folder is its category's, with two exceptions:

| Discipline | Categories |
|---|---|
| `structure` | `beam`, `column`, `foundation`, `slabEdge`, `rebar`, `mesh` |
| `systems` | `pipe`, `duct`, `terminal` |
| `site` | `terrain`, `grading`, `propertyLine` |
| `architecture` | every other category |

1. An element that carries `frameHost` (a materialised framing member or truss) goes in `structure`.
2. A `component` element goes in `structure` when it is `structural`, or when its type is a plate (`component.family` is `"plate"`), a cast-in item (`insert`) or a connector of the writer's own library; otherwise in `architecture`. A `component` type goes in `structure` when it is a plate, a cast-in item or a connector, otherwise in `architecture`.

Walls, slabs and roofs stay in `architecture` even when they bear: the folder follows who places them, not what they carry.

3. A writer of 0.1 MUST place records by these rules, and a writer of 0.2 by section 4.4.
4. A reader MUST NOT derive a record's meaning from its folder. It MUST accept a `component` record in either `architecture` or `structure`, since which components are connectors is the writer's library.
5. A `generic` element goes in the folder its `generic.discipline` names (its own, else its type's), and in `architecture` when neither names one; a `generic` type goes in the folder its own `generic.discipline` names, else in `architecture` (Part II, 6.6). A reader MUST accept a `generic` record in any discipline folder.
6. A reader MUST accept an element or a type of a category it does not know (Part I, 10, rule 5) in any discipline folder. A writer MUST write it back at the path it was read from, and, where it has none (the single-file form), under `architecture`: `architecture/<level>/<category>/<id>.json`, `types/architecture/<category>/<id>.json`.

Reference: `categoryDiscipline`, `elementDiscipline` and `typeDiscipline` in `src/core/versions/layout.ts`.

*Section 4.4 replaces this table and rules 1 and 2 in 0.2: there a record's folder is the discipline that owns it, one of eight, and rules 4 to 6 hold for the eight folders.*

### 4.2 Element, type and view paths

- An element: `<discipline>/<segment(levelId)>/<category>/<segment(id)>.json`.
- A type: `types/<discipline>/<category>/<segment(id)>.json`.
- A view: `drawings/<discipline>/views/<kind>/<segment(id)>/view.json`, where `kind` is the view's `kind`.
- A sheet: `drawings/<discipline>/sheets/<segment(id)>.json`.

`<discipline>` is the record's owner (section 4.4). In 0.1's layout (section 4.1) it is the discipline of section 4.1, a view is at `drawings/views/<kind>/<segment(id)>/view.json` and a sheet at `drawings/sheets/<segment(id)>.json`.

A writer MUST write each record at its path. A reader SHOULD report a record whose file name, level folder or category folder disagrees with the record; the record's own members win. A validator MUST report such a record as an error, naming its file, and a view whose folder or kind folder disagrees with it too.

*Note (informative).* The reference reader takes a record's members as they are and does not compare them with the path. `tools/check.mjs` does.

### 4.3 Names that fold to one

Some file systems ignore letter case, and some Unicode normalisation too: there `Wall-A.json` and `wall-a.json` are one file, and so are two names that differ only in how an accented letter is encoded, as one code point or as a letter and a combining mark. A folder that holds both names cannot be written there, nor checked out there ([Security and privacy, 6.2](https://openeik.org/spec/0.2/security-and-privacy#6.2)).

1. The **fold** of a name is the name in Normalization Form D ([UAX #15](https://www.unicode.org/reports/tr15/tr15-50.html), revision 50, of Unicode 13.0.0), each of its characters replaced by its full case folding, and the result in Normalization Form C, each step as [Unicode 13.0.0](https://www.unicode.org/versions/Unicode13.0.0/) defines it. The full case folding is that of the Unicode Character Database's `CaseFolding.txt` of that version: its mappings of status C and F, and not those of status T, which are for Turkic languages. A code point that Unicode 13.0.0 does not assign has, in that version, no case folding and no decomposition, and is a starter that composes with no other character: it stays as it is. Two names **fold to one** when their folds are equal: they are then canonical caseless matches (the Unicode Standard, section 3.13, D145).
2. Two paths **fold to one** when they differ and, compared name by name from the first, the first two names in which they differ fold to one: where names fold, the two are one file, or lie in one folder that they spell in two ways. `architecture/level-0/wall/Wall-A.json` and `architecture/level-0/wall/wall-a.json` fold to one, and so do `architecture/Level-1/wall/a.json` and `architecture/level-1/roof/b.json`.

Part I, 6, rule 7 keeps two records' paths from folding to one, and section 7, rule 8 a kept file's path from folding to one with another of the model's.

*Note (informative).* This section was decided by the steward on 15 October 2026 (open questions, 23). Under rule 1, `ß`, `ẞ`, `SS` and `ss` fold to one, as do the Kelvin sign (U+212A) and `k`, and `Σ`, `σ` and `ς`; the Turkic dotless `ı` and `i` do not, nor do `wall-a` and `wall-à`. File systems that ignore case do not all fold alike, and a few hold names to be one that this rule keeps apart (the dotless `ı` and `i`, by their upper case); the rule is the Unicode Standard's own, so that every implementation compares names alike. Folding the decomposed name before it is composed again catches the few characters whose folding depends on the order of their combining marks, such as `ᾴ`.

*Note (informative).* Rule 1 names a version of Unicode so that every implementation folds a name alike, whatever version its platform has: a later version folds some characters that 13.0.0 does not assign (16.0 folds U+A7CB to U+0264, and U+A7DC to U+019B) and decomposes others (16.0 decomposes U+105C9), and a platform's own case mappings and normalisation follow its own version. Normalisation is stable for the characters that a version assigns (normalization stability, in [the Unicode Character Encoding Stability Policies](https://www.unicode.org/policies/stability_policy.html)), so a platform whose Unicode is 13.0.0 or later normalises a name of 13.0.0's characters as 13.0.0 does. An implementation on such a platform that leaves each later character of a name as it is, normalises the runs of characters between them apart and folds their case by 13.0.0's `CaseFolding.txt` folds every name as 13.0.0 does. A platform of an earlier version lacks some of 13.0.0's characters, and needs 13.0.0's normalisation data for them too. The version was fixed on 15 October 2026, as a reading of the decision, after a review found that current platforms fold U+A7CB differently (open questions, 23).

### 4.4 Folders that follow owners

*Part of 0.2, with [Part XVI](https://openeik.org/spec/0.2/ownership). It writes open question 85, which the steward decided on 27 October 2026 for 0.2 ("85y"). OpenEik 0.1 had it with the proposal `ownership`: a 0.1 writer writes the folders of section 4.1, and a 0.1 reader keeps a file in a folder that only this section gives, `ventilation/`, `plumbing/`, `electrical/`, `sprinkler/`, `setup/` or `drawings/<discipline>/`, as a kept file (section 7).*

Section 4.1 files a record by its category, and keeps a wall, a slab or a roof in `architecture` even when it bears: the folder follows who places a record. Part XVI gives ownership by path, and its parties work in eight disciplines (Part XVI, 2, rule 4). In this section a record's discipline folder is the discipline that **owns** it, one of the eight: `architecture`, `structure`, `ventilation`, `plumbing`, `electrical`, `sprinkler`, `site` and `setup`. So a party owns its discipline's folders, and an owners file says who owns what by a plain glob (Part XVI, 3.1). One set of rules, rules 1 to 5, gives a record's owner and so its folder: the application that checks a change, the host that holds it for approval and a tool that knows only paths give the same answer.

1. **An owner set by hand.** An element, a type and a view MAY carry `owner`, one of the eight disciplines, set by hand. It wins over every rule below. A validator MUST report an `owner` that is none of the eight as an error; [`schemas/0.2/ownership/owner.schema.json`](https://openeik.org/schemas/0.2/ownership/owner.schema.json) states this rule.
2. **An element's owner.** An element that carries no `owner` is owned, by the first of these that holds:
   - `structure`: an element that carries `frameHost` (a materialised framing member or truss); a wall, a slab or a roof that is `structural`; a stair that is `structural`, or that carries no `structural` and whose type's `stair.construction` is `concreteCast`, `concretePrecast` or `clt`; an element of the categories `beam`, `column`, `foundation`, `slabEdge`, `rebar` and `mesh`, and `member` ([Part II, 6.2.1](https://openeik.org/spec/0.2/language#6.2.1)); and a `component` that section 4.1, rule 2 puts in `structure` because it is `structural` or because its type is a plate or a cast-in item;
   - `ventilation`: a `duct`, and a `terminal` that is an air device (rule 3);
   - `plumbing`: a `pipe`, and every other `terminal`;
   - `electrical`: an element of the categories `electrical`, `cableCarrier` and `cable` ([Part XIII](https://openeik.org/spec/0.2/electrical));
   - `site`: an element of the categories `terrain`, `grading`, `propertyLine`, `planting`, `road` and `parking`;
   - a `generic` element: `structure` or `site` where the `generic.discipline` that section 4.1, rule 5 reads (its own, else its type's) names it, else `architecture`;
   - a `provision` ([Part II, 6.10](https://openeik.org/spec/0.2/language#6.10)): its asker's, the owner this rule gives the run its `provision.forIds` names first, else `setup`;
   - `architecture`: every other element.

   A `component` that this rule gives no owner, which may be a connector of its writer's library (section 4.1, rules 2 and 4), and an element of a category the reader does not know (Part I, 10, rule 5), stay in the folder they were read from where that is one of the eight, and are `architecture`'s where it is not or where they have none (section 4.1, rule 6).
3. **An air device.** A terminal is an air device when its type's `terminal.kind` is a component in a duct (`damper`, `fireDamper`, `smokeDamper`, `flowRegulator`, `silencer` or `ductFan`; Part II, 6.9), `transferAir` or `ftxUnit`; when the kind is `heatPump` and one of its type's connections carries air; and otherwise when its system carries air (Part II, 6.9's table). A component's system is its run's (Part II, 6.9): the run's `mep.system`, else its type's `pipe.system` or `duct.system`, and a duct carries air where neither gives one. Any other terminal's system is its type's `terminal.system`, else the system of its type's first connection. A terminal type is an air device by its kind, as above, or, where its `terminal.mount` is not `pipe`, by its own system. A terminal without a type is no air device.
4. **A type's owner** is its `owner`. Else it is `structure` for a wall, slab, roof or stair type that an element uses which bears by its own members, a wall, a slab or a roof that is `structural` or a stair as rule 2 says, and which carries no `owner`: the type's layers, or its flights, are the bearing build-up. Else it is the owner its category gives: a `terminal` type `ventilation` when it is an air device (rule 3), else `plumbing`; a `component` type `structure` when it is a plate or a cast-in item, else, as an element of its type, where it was read from (rule 2); a `generic` type by its own `generic.discipline`, as rule 2 says; a `provision` type `architecture`'s; and the type of any other category as rule 2 gives an element of that category. So a type's folder follows its elements: a wall type moves to `types/structure/` when a wall that uses it is made `structural`, and back when none is.
5. **A view's and a sheet's owner.** A view's owner is its `owner`, else `setup`. A sheet's owner is that of its discipline code: its `discipline` ([Part XIV, 2.1](https://openeik.org/spec/0.2/drawings#2.1)), else the letters its `number` begins with, one to four of the letters A to Z, Å, Ä and Ö in either case, where a `-`, a space or a `.` follows them or the number ends, in upper case. `A` and `BR` are `architecture`'s, `K` `structure`'s, `V` and `VE` `ventilation`'s, `VS` `plumbing`'s, `E` `electrical`'s, and `M`, `L` and `G` the `site`'s; any other code, and none, `setup`'s. `sprinkler` and `setup` own an element or a type only by its `owner`, but for a provision that names no run of the model, which is `setup`'s (rule 2).
6. **The folders.** In the **owners' layout**:
   - an element: `<owner>/<segment(levelId)>/<category>/<segment(id)>.json`;
   - a type: `types/<owner>/<category>/<segment(id)>.json`;
   - a view: `drawings/<owner>/views/<kind>/<segment(id)>/view.json`, with its detail items in chunks beside it (section 10);
   - a sheet: `drawings/<owner>/sheets/<segment(id)>.json`.

   The other lists keep their folders (section 1): `project/` is the project setup's, `fabrication/` structure's, `project/circuits/` electrical's (Part XIII, 3.1), and `documents/` holds the records any writer adds. This replaces section 4.1's table and its rules 1 and 2; its rules 3 to 6 hold for the eight folders and for `types/` of each, with `architecture` where 4.1 says it. `architecture`, `structure` and `site` are folders of both layouts; `systems`, `drawings/views/` and `drawings/sheets/` are 0.1's alone; `ventilation`, `plumbing`, `electrical`, `sprinkler`, `setup` and `drawings/<discipline>/` the owners' alone, and Part I, 11.1, rule 2 reserves them.
7. **Reading and writing.** A writer of 0.2 writes the owners' layout, each record at its path in it, and writes a folder in one layout. A reader MUST accept a record in either layout, 0.1's or the owners': no path is a record's in both layouts with two meanings, and section 4.1, rule 4 already forbids reading meaning from a folder. A folder read in either layout gives the same model, record for record and hash for hash. A validator MUST report a record in a folder that neither layout gives it as an error (section 4.2), and SHOULD warn about a folder that holds records in both: one where only 0.1's layout puts it, and another where only the owners' does.
8. **The change of layout.** The first version a writer makes of a building in the owners' layout moves every record whose folder changes, once, a view with its detail chunks: for a building of 0.1, the version that migrates it to 0.2, in one write (Part I, 9.8, rule 1); for a folder of 0.2 still in 0.1's folders, a version that only moves. Its message says that it changes the layout. It SHOULD change no record but by moving it, and but those Part I, 9.8 rewrites, so that its diff shows each other record moved (Part VI, 4, rule 3). A record it moves touches the path it leaves and the path it takes (Part XVI, 4, rule 1), so it needs the owners of both by the owners file as it stood before; it SHOULD be made by a party that owns `**`. In the same version, a writer rewrites `project/owners.json`, where the building has one, for the new folders (Part XVI, 3.1). Every later version has the paths of its records' owners. Versions already stored are never rewritten (Part VI, 3, rule 2), and a reader reads each in its own layout (rule 7).
9. **What changes, and what stays.** The change of layout moves records and changes none; the migration to 0.2 in the same version rewrites only what Part I, 9.8 says. Each record's id, its members, the GlobalId an export gives it (Part VIII) and its hash (section 6) stay what they were: its object is the same object in every version. What changes is each moved record's path, so every tree that holds one, and the root tree of the migrating version and of every version after it; and every digest of pairs (Part VI, 8.3), whose paths are leaf paths. A reader that compares a pair with a version (Part VI, 8.4; Part XIV, 9) compares it with the record its path names in either layout, its list and its id, so that a record that only moved, its hash the same, is unchanged.

*Note (informative).* What a path does not say stays an application's in 0.2 (open question 85, point 4). Three rules of the reference implementation are finer than a record: the fields of a record that another discipline owns (the framing of a wall that architecture owns is structure's); the answer to a provision for a void (Part II, 6.10), which the owner of its host gives; and a record that only follows its host (studs moved with their wall), which the host's owner may change. A host that enforces paths asks the follower's owner too, and does not see the fields. An application MAY refuse more than its owners file says; it SHOULD NOT accept a change that the file's paths refuse without saying so.

*Note (informative).* The cost of the layout is a migration of every tree, once, and a type whose folder follows its elements; its gain, that one function decides a record's owner and its folder. On the reference implementation's seven example projects, 3 288 records were compared: its own owner functions and `@openeik/tools` give every one the same owner, and the change of layout moves from 10 records (the views and sheets of a balcony) to 368 (a villa with its electrical installation, whose 213 devices leave `systems/`). Rules 2 to 5 are the reference's `elementOwner`, `typeOwner` and `sheetOwner` but in one point, which does not arise in its examples: it gives a `generic` record whose discipline is `systems` to `setup`, where rules 2 and 4 give it to `architecture`, `systems` naming none of the eight; `owner` gives it to whom it belongs.

Reference: `elementOwner`, `typeOwner`, `bearingTypeIds` and `sheetOwner` in `src/core/access/owners.ts`, `terminalIsAir` and `terminalTypeIsAir` in `src/core/elements/mep.ts`, `stairCarries` in `src/core/views/disciplines.ts` and `sheetDiscipline` in `src/core/sheets/disciplines.ts` (the reference implementation's work 670, at its `main` `f2bc53d2`), which decide a record's owner; the reference implementation still writes the folders of section 4.1 (open questions, section 2). In this repository, `modelOwners` in `packages/openeik/src/layout.js`, `migrateFiles` and `migrateFolder` in `src/migrate.js`.

## 5. The stored form

Every JSON file a writer writes is in the **stored form**, so that the same record always gives the same bytes and a changed value is a one-line diff:

1. UTF-8 without a byte-order mark, and a final newline.
2. Object members sorted by name, comparing UTF-16 code units; members whose value is undefined are left out.
3. An object with members is written as `{`, a newline, one member per line indented two spaces deeper than the object as `"name": value`, the members separated by `,` and a newline, a newline, and `}` at the object's own indent. An empty object is `{}`.
4. An array whose items are all scalars (strings, numbers, booleans, `null`) is written on one line: `[` the items separated by `, ` `]`. An empty array is `[]`. Any other array has one item per line, indented as object members are.
5. Scalars are written as ECMAScript's `JSON.stringify` writes them: strings with its escapes, numbers in the shortest form that reads back as the same double, `-0` as `0`.

A point list is therefore one point per line, and a point is one line:

```json
{
  "category": "wall",
  "height": 2700,
  "id": "wall-south",
  "profile": [
    [10000, 0],
    [0, 0]
  ],
  "top": {
    "attach": {
      "face": "underside",
      "hostId": "roof-main"
    },
    "constraint": "unconnected"
  }
}
```

(abridged from `examples/one-storey-house/architecture/level-0/wall/wall-south.json`.)

6. A writer MUST write the stored form. A reader MUST accept any layout of a record, its white space and its members' order, that is I-JSON (RFC 7493): the form is for diffs, not for parsing. A reader SHOULD refuse a record that is not I-JSON, as Security and privacy, 7.3, rule 1 asks: an object with a member name twice, a string with an unpaired surrogate, or a number that expresses more magnitude or precision than a double provides, as that rule says. A validator MUST report a JSON file of the model's own that is not in the stored form, a record's, `project/order.json` or `eik.json`, as an error, naming the file: a reader reads it all the same.

Reference: `eikText` in `src/core/versions/eikFolder.ts`; `tools/lib/stored-form.mjs` in this repository is an independent implementation, used by `tools/check.mjs`.

## 6. Canonical form and hashes

1. The **canonical form** of a JSON value is the JSON Canonicalization Scheme of [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785): no white space, members sorted by UTF-16 code units, scalars as ECMAScript writes them.
2. A record's **hash** is the SHA-256 of the UTF-8 bytes of its canonical form, written as 64 lowercase hexadecimal digits.
3. The stored form and the canonical form of a record differ only in white space, so a record's hash does not depend on how its file is laid out.
4. A kept file's hash is the hash of its text as a JSON string (the canonical form of the string).

Reference: `canonicalJson` in `src/core/versions/canonical.ts` (keys sorted, numbers as JavaScript writes them, absent values left out); `sha256` in `src/core/versions/sha256.ts`.

## 7. Kept files

A folder may hold files that are not records: another tool's results, requirements, a README. They travel with the model.

1. A **kept file** is a file at a plain relative path (no segment starting with `.` or `~`, no `\` and no NUL) that is not `eik.json`, not `AGENTS.md` and not at a record's path. Rule 8 keeps some plain paths from kept files too.
2. A kept file of at most 1 MiB (1 048 576 bytes) that is valid UTF-8 without NUL bytes is read as text and kept with the model. A writer MUST write it back unchanged where it was. A larger or binary file is left alone: not part of the model, never removed.
3. A writer of the model MUST NOT remove or rewrite a kept file, and MUST remove only the files of records that went. The application that owns a kept file may change or remove it.
4. A kept file MUST NOT occupy a record's path, that path without `.json` (the record's leaf in a version tree, Part VI, 2), or a folder a record needs; a reader MUST refuse such a folder and name the path.
5. In the single-file form (section 11) kept files are the `extras` member: text by path.
6. A kept file is not a record, and the units of Part I do not bind it. An application that keeps results in a folder (an analysis, a quantity take-off) MUST state their units in the files themselves; they need not be millimetres.
7. An application keeps its own files in a folder of its own at the top of the model's folder, `<application>/`, which Part I, 11.3 says to name by its prefix, with a model folder for each model of its own, `<application>/<model>/` (for example `acme/frame-a/`). Part VI, 8, says how it keeps its results, with their result manifests, and its proposals there.
8. A kept file's path is not escaped, and MUST NOT hold a name that a file system refuses or reads otherwise, which section 2 escapes in a name made from an id (its rules 2 to 5: a character a common file system refuses, a leading `.` or `~`, a trailing `.` or space, a device name), nor fold to one (section 4.3) with a record's path, with `eik.json` or `AGENTS.md`, whether or not the folder holds them, or with another kept file's path, nor pass through `eik.json` or `AGENTS.md`, or a name that folds to one with either, as through a folder: each is a file at the top of the model's folder (section 8). A writer MUST NOT write such a kept file: it refuses the model, naming the path, before it writes anything. A file at such a path in a folder is no kept file, and a reader MUST NOT refuse the folder for it. It leaves the file alone, as rule 2 leaves a larger or binary file: not part of the model, and never removed. It SHOULD warn, naming the path, and reads on. It takes the records' paths, `eik.json` and `AGENTS.md` first, and then the kept files in the order of their paths by UTF-16 code units, and leaves alone each kept file whose path folds to one with a path it took before it, or that passes through `eik.json` or `AGENTS.md`. A validator MUST report each such file of a folder as a warning, and each such entry of the `extras` of a model in the single-file form as an error, naming its path. A reader of versions MUST refuse a version that holds such a kept file, naming it (Security and privacy, 5.3, rule 2), unless its caller asks it to repair the version, when it reads the version, leaves the file out and gives it back, losing nothing (5.3, rule 4).

Reference: `keptPath` and `extrasSchema` in `src/core/model/extras.ts`; `textOf` and `KEPT_MOST` in `server/eikSession.ts`; `manifestOfFolder` ("a record of the model is kept at that name"); `writeFolder`.

*Note (informative).* Rule 8 was decided by the steward on 15 October 2026 (open questions, 25). A kept file is another tool's text, not a record (rule 6), so a validator reports such a file of a folder as a warning, not an error: the folder reads and checks on the system that holds it, and the file is in no version, so nothing but a checkout of the folder itself on a system that refuses or folds the name can fail. In the single-file form such an entry is part of the model as it was given, which no writer writes and a reader cannot leave alone without losing it, so it is an error there. The order of rule 8 gives every reader one model: where names fold, the second of two kept files could not be there at all. So `notes/con.txt`, `notes/12:30.md`, `C:/notes.md` and `notes/draft.` are left alone, as are `Types/notes.md` beside the folder `types/`, `notes/Readme.md` beside `notes/README.md`, which comes before it by code units, and `agents.md` in a folder without `AGENTS.md`, where a writer would write it (section 8.2), and `AGENTS.md/notes.md` in a folder named `AGENTS.md`, which takes the place of that file. No folder gives a version that holds such a kept file, since a reader of the folder leaves the file out; and a reader that left it out of a version it read would drop it from the next version it stores (Part VI, 6.3, rule 6). So a reader of versions refuses such a version, as it refuses a tree with a name that no folder holds, and whoever made it renames the file. Two kept files whose paths fold to one, one from each side of a merge, are a conflict (Part VI, 5, rule 5). A `%` is no such character, as a reading of the decision: Part VI, 8.1 names an application's folders by the segments of its own ids, escapes and all (`acme/frame%3Aa/source.json`), and those folders hold kept files; section 2 escapes a `%` only so that an escape reads back as one (its rule 1), and a kept path is never read back. So `notes/50%.md` is a kept file too. The reference implementation keeps such files as kept files, and holds them in its versions (open questions, section 2).

## 8. The engine files

### 8.1 `eik.json`

`{"format": "openeik/0.1-draft", "units": "mm"}`, in the stored form. Part I, 9.2, says what a reader does with it. Writers MAY add members in later versions; readers MUST ignore members they do not know.

A writer MUST keep the members of `eik.json` that it does not know: writing into a folder or a version whose `eik.json` it read, it writes them back as it read them, beside the `format` it writes (Part I, 9.2, rule 2) and `units`. No application adds one (Part I, 11.2, rule 8).

*Note (informative).* The writer's rule was decided by the steward on 15 October 2026 (open questions, 27). A member of `eik.json` that a 0.1 writer does not know is a later version's, which a reader of the same major version reads from 1.0 on (Part I, 9.6, rule 2), or one that an application should not have added (`@openeik/tools` warns about it, `eik-extension`): either way it stays, as the members of a record that a reader does not know stay (Part I, 10, rule 1). The single-file form holds no `eik.json` (section 11): a writer that writes it out as a folder writes its own.

### 8.2 `AGENTS.md`

The conventions an agent cannot read off the files: units, level semantics, the meaning of profiles, that nothing derived is stored, that a hand edit adds 1 to the revision, and how to check the folder.

1. A writer MUST write `AGENTS.md` when the folder has none, and MUST NOT overwrite one that exists: a person's edits to it stay.
2. A reader MUST NOT read model data from it.

Reference: `AGENTS_TEXT` in `src/core/versions/eikFolder.ts`; `writeFolder` writes it only where there is none.

## 9. Order

1. `project/order.json` maps the name of each record list other than `elements` that the model has (`types`, `views`, `materials`, `sheets` and so on) to its ids in order.
2. A reader orders each list by it. Records it does not name follow, in path order; ids it names without a record are ignored.
3. The order of elements is not part of a model. Implementations MUST NOT give it meaning.
4. Levels keep their order in `project/levels.json`.
5. `project/order.json` is an index of the record lists, not a record: it names lists and their ids, and nothing else (Part I, 11.2, rule 8), and the single-file form has no counterpart for it (section 11, rule 4). Part I, 10, rule 1 does not hold for a member of it that names no record list the reader knows: a reader MAY drop such a member, and SHOULD warn when it does. A validator SHOULD warn about one, and MUST NOT refuse the folder for it.

Reference: `buildTree` and `readManifest` in `src/core/versions/tree.ts`.

*Note (informative).* In 0.1 such a member is always one that no writer should have written: a reader of 0.1 refuses a folder of a later minor version (Part I, 9.2, rule 3), so it never meets a later version's list there. `@openeik/tools` drops such a member and warns (`order-unknown`).

## 10. A view's detail items

A view's 2D items (lines, texts, dimensions, tags and the rest) can number tens of thousands. They are stored in chunks beside the view, so that an edit rewrites one chunk:

1. When a view has detail items, `view.json` holds the view without them, and the items go into chunk files `details/<n>.json`, in order. A view with an empty list keeps `"details": []` in `view.json`.
2. A chunk ends after an item whose id's hash is 0 modulo 64, or when it holds 256 items. The hash is 32-bit FNV-1a over the id's UTF-16 code units: start from `0x811c9dc5`; for each code unit, XOR it in, then multiply by `0x01000193` modulo 2³². This is not quite the FNV-1a of RFC 9923, which hashes octets: the two agree only for ids of ASCII characters.
3. Chunks are named by their index from 0 in decimal, zero-padded to the width of the largest index or 4 digits, whichever is more: `0000.json`, `0001.json`, ….
4. A writer MUST chunk by this rule. A reader MUST join a view's chunks in name order and MUST accept any chunking.

Reference: `detailChunks` and `HashCache.viewParts` in `src/core/versions/tree.ts`.

## 11. The single-file form

A model may also travel as one JSON object, for exchange and email. It is the folder's content without the layout:

`{format, units, project, projectInformation?, levels, grids?, scopeBoxes?, materials?, dimensionStyles?, detailFamilies?, titleBlocks?, images?, types, elements, views, sheets?, circuits?, people?, reports?, renders?, deviations?, areaAdjustments?, references?, pieces?, joints?, originals?, copies?, extras?}`

In 0.1 the object began with `schemaVersion` and named no format; 0.2 deprecates `schemaVersion` (Part I, 9.3).

1. Lists hold their records in order; views hold their detail items inline; `extras` holds the kept files.
2. `schemas/model.schema.json` validates it.
3. A reader that reads folders MUST also read the single-file form. Reading a folder and writing it as one file, or the reverse, MUST lose nothing but the order of elements, the files a reader leaves alone (section 7, rules 2 and 8), the members of `project/order.json` that name no record list (section 9, rule 5) and the members of `eik.json` other than `format` and `units` (section 8.1), since the single-file form holds those two only (rule 5).
4. `project/project.json` holds the members of the single-file object other than its record lists, `levels`, `extras`, `format` and `units`: `project`, `projectInformation`, and any other member at the top of either (`schemaVersion` among them, where a model gives it), a member a reader does not know or an extension member (Part I, 10 and 11.2). Reading and writing carry such a member from the one to the other. `project/order.json` has no counterpart in the single-file object: there the order of a list is the order of its records (section 9), and the file, an index and not a record, holds nothing else (section 9, rule 5; Part I, 11.2, rule 8).

5. The object names its format and its units at its top, `format` and `units`, as `eik.json` does for a folder (section 8.1; Part I, 9.2). A writer of 0.2 MUST write both. A reader reads the object's version from `format` and refuses one it does not implement, as it refuses such a folder (Part I, 9.2, rule 3), and refuses `units` other than `"mm"` (Part I, 3, rule 7). An object without `format` is one of 0.1, which named none.

Reference: `ek import` (folder to one file) and `ek export` (one file to folder) in `server/ek.ts`; the manifest type in `src/core/model/model.ts`. The reference implementation writes no `format` in its single-file models yet (open questions, section 2).

### 11.1 The file extension and the media type

1. A file that holds a model in the single-file form SHOULD have a name that ends in `.eik.json` (`one-storey-house.eik.json`): it ends in `.json`, so that every tool opens it as JSON, and `.eik` comes before that, so that people and tools tell a model from other JSON.
2. Its media type is `application/vnd.openeik+json`: JSON text (RFC 8259) in UTF-8, with the `+json` structured syntax suffix (RFC 6839), and without parameters. A server, a store or a tool that labels a model in the single-file form with a media type (an HTTP `Content-Type`, an upload, an attachment) SHOULD use it.
3. A reader MUST read the single-file form whatever its file is called and however it is labelled (as `application/json`, say, or in a file named after the application that wrote it): a name and a label say what a file is meant to hold, and its content decides.
4. The media type is not registered with IANA yet. Its registration is planned before 1.0 ([`GOVERNANCE.md`](https://gitlab.com/openeik/spec/-/blob/main/GOVERNANCE.md#reaching-10)), with the template of section 11.2.

*Note (informative).* The file extension and the media type were decided by the steward on 14 October 2026 (open questions, 20). The reference implementation names its own single-file models `*.stomra.json` and labels none with a media type; this text does not change what it writes (open questions: section 1, 24, and section 2, 21). A folder has neither a file extension nor a media type, and 0.1 gives none to an archive of a folder (a zip).

### 11.2 The media type's registration template (informative)

*This section is informative. It is the template to be sent to IANA to register the media type of section 11.1 in the vendor tree (RFC 6838, sections 3.2 and 5.6). It has not been sent.*

```text
Type name: application

Subtype name: vnd.openeik+json

Required parameters: none

Optional parameters: none

Encoding considerations: binary. The content is JSON text (RFC 8259),
  which is UTF-8 when it is exchanged (RFC 8259, section 8.1).

Security considerations: A model is data: JSON text, which names no
  resource outside itself that a reader must fetch. The security
  considerations of JSON (RFC 8259, section 12) apply. A model can be large
  (many records, points, detail items and meshes); a reader bounds the
  memory and the time it spends on one. Some of its strings are
  expressions that a compiler evaluates: the arithmetic of the framing
  families (OpenEik, Part II, section 5.4), a closed language of numbers,
  names, the four operations and eight functions, with no loops, no calls
  out of it and no state, at most 240 characters long; an implementation
  evaluates them by that grammar alone, never as a programming language's
  code. Its kept files (the member "extras") are other applications' text
  by path; a reader keeps their text and does not run it, and an
  application that interprets one treats it as untrusted input. Written
  out as a folder, a model names the paths of its files: a record's is
  made of its id, its level's id, its category and, for a view, its kind
  (OpenEik, Part III, sections 2 and 4), and a kept file's is its own. A
  kept file is a file at a plain relative path: no name in it starts with
  "." or "~", so none is "..", and it holds no "\" and no NUL (Part III,
  section 7.1). A conforming writer, and any tool that writes a model's
  files (a clone, a pull, the single-file form written out as a folder),
  writes no file outside the model's folder (OpenEik, Security and
  privacy considerations, section 6.1): it escapes each name it makes
  from an id or a level's id (Part III, section 2); a category and a
  view's kind are plain names by their schemas; and it refuses the
  model, before it writes anything, where a kept file's path is not
  plain, holds a name that a file system refuses or reads otherwise
  (as a drive, a device or a second stream of a file, or with a
  trailing dot or space dropped), folds to one with another of the
  model's paths where a file system ignores case and Unicode
  normalisation, or passes through "eik.json" or "AGENTS.md" as
  through a folder (Part III, sections 4.3 and 7). So no path that such
  a tool builds from a model resolves outside the model's folder, and
  no two of them are one file. Content hashes (SHA-256) name
  content; they do not say who wrote it. A model may hold personal data,
  such as its authors' names and email addresses and a site's position,
  and gives it no protection of its own: whoever shares a model shares
  them.

Interoperability considerations: A model in this form holds the same
  content as an OpenEik folder (OpenEik, Part III, sections 1 and 11).
  Readers keep the members they do not know (Part I, section 10) and accept
  any JSON layout (Part III, section 5). Files written before this
  registration may be named "*.json", or after the application that wrote
  them; a reader reads the form whatever its file is called.

Published specification: OpenEik, Part III, section 11, "The single-file
  form" (https://openeik.org/). The URL of the published version is given
  at registration.

Applications that use this media type: building modelling and design
  applications, model checkers and validators, agent hosts, and exporters
  that read or write OpenEik models.

Fragment identifier considerations: as for application/json (RFC 6839,
  section 3.1). OpenEik defines none.

Additional information:

  Deprecated alias names for this type: none
  Magic number(s): none
  File extension(s): .eik.json
  Macintosh file type code(s): none

Person & email address to contact for further information: the steward of
  OpenEik (GOVERNANCE.md); the address is given at registration.

Intended usage: COMMON

Restrictions on usage: none

Author: the editors of OpenEik

Change controller: the steward of OpenEik, and the body that holds OpenEik
  after it (GOVERNANCE.md)
```

## 12. Reading a folder

A reader:

1. reads `eik.json` and checks the format (Part I, 9.2);
2. walks the folder, skipping hidden names, and sorts each file into a record, an engine file or a kept file (sections 3 and 7);
3. parses each record file, and refuses a file that is not JSON, naming its path;
4. assembles the model: the project, the levels, each list ordered by `project/order.json`, each view joined with its chunks, the kept files;
5. performs the **whole-model check**, where it does (below): validates every record against its schema and the whole model against Parts I and II, and refuses the folder, naming the file and the rule, when any fails; a record of a category it does not know it validates against the common schema only, keeps and warns about (Part I, 10, rule 5).

The whole-model check of rule 5 is required of the validator class, which reports what fails it (Conformance, 1), and a reader SHOULD perform it. A reader that performs the check MUST refuse a folder that fails it. A reader that does not perform the check reads the folder as it is. Every reader MUST refuse what it cannot read, which is what this list names; a reader that does not perform the check need refuse nothing else:

- a folder whose `eik.json` is not JSON, has no `format`, names a version it does not implement or gives `units` other than `"mm"`, or that has no `eik.json` where Part I, 9.2, rule 5 does not let it read it as `openeik/0.1-draft` (Part I, 3, rule 7, and 9.2);
- a project whose `schemaVersion` it does not implement (Part I, 9.3), or that gives `units` other than `"mm"` or an `upAxis` other than `"Z"` (Part I, 3, rule 7);
- a record file that is not JSON (rule 3), or that holds a number that is not finite (Part I, 3, rule 5);
- a record file, `eik.json` or a model in the single-file form that is not valid UTF-8 (section 5, rule 1): no text, which it cannot read;
- two records of one id in a list that Part I, 6, rule 1 names: elements, types, views, levels, grids, sheets or linked references;
- a kept file at a record's path or where a record needs a folder (section 7, rule 4);
- a file it cannot read whole (Security and privacy, 7.2, rule 2);
- an object or a tree from a store that Security and privacy, 5.3 refuses, among them a version that holds a name that no folder gives, a hidden one or one that starts with `~` and is no shard, or a kept file that a reader of a folder leaves alone or reads as another file (section 7, rules 1, 2, 4 and 8), unless it is asked to repair the version (Security and privacy, 5.3, rule 4).

A reader SHOULD also refuse a model's file that is not I-JSON, with or without the check: an object that repeats a member name, a string with an unpaired surrogate, or a number that expresses more magnitude or precision than a double provides (section 5, rule 6; Security and privacy, 7.3, rule 1).

A reader MUST NOT add, change or drop records while reading, except as Part II says for legacy members (6.4) and as this part says for kept files. It MUST keep members it does not know, and records of categories it does not know (Part I, section 10).

**Types are in the folder.** A conforming folder contains a type record for every type any of its records names. A reader MAY rely on `types/**` alone for types; it MUST NOT need a built-in library to read a conforming folder.

Reference: `modelOfFiles` and `readFolder` in `server/eikSession.ts`; `manifestOfFolder` in `src/core/versions/eikFolder.ts`; `parseImport` in `src/core/model/store.ts`.

*Note (informative).* What this section says of the whole-model check was decided by the steward on 15 October 2026 (open questions, 29). Each refusal of the list is a rule of its own elsewhere in this specification, and binds every reader. The check adds the schemas and the rules of Parts I and II: a reader that performs it refuses a model that fails them as well, and one that does not reads such a folder as it is and leaves the verdict to a validator. A model that fails the check alone, a member in the wrong place, a size that disagrees with its type or a reference that does not resolve, is one that a reader may read or refuse, as it performs the check or not. A validator reports more than the check covers: among it, a single-file model's kept file at a path that section 7, rules 1 and 8 keep from kept files, which a writer refuses to write. No rule has a reader refuse such a model, and a reader may read it as it is, the entry kept, or refuse it as a validator would. The reference reader performs the check, and so do `@openeik/tools`' `readFolder`, `readVersion` and `readModel`, unless told not to (`check: false`).

*Note (informative).* The reference reader goes further than this part allows on two points. It adds its built-in library of types, title blocks and detail families to every model it loads, so it would also read a folder that names a built-in type without containing it. And it migrates older records to the current form. A folder the reference writer writes always contains every type it names. Since Stomra 641 it leaves out the library's types that nothing names (section 13), and puts each back when it reads the folder again, by its id in `project/order.json`, where an id without a record is ignored (section 9, rule 2). The reference implementation's own single-file examples do not all conform: six of its nine `examples/*.stomra.json` name a built-in gutter type (`type-gutter-halfround-125-zinc`) that they do not contain. Exported with `ek export`, all nine conform. See the open questions.

## 13. Writing a folder

A writer:

1. writes every record at its path (section 4) in the stored form (section 5);
2. writes every type that any record names, and SHOULD NOT write library records the model does not use (below);
3. writes `eik.json`, keeping the members of the folder's own that it does not know (section 8.1), and `AGENTS.md` when there is none;
4. removes the file of each record that is no longer in the model, or no longer at that path (a record moved to another level, or read at a name in an older form, section 2, rule 9), and no other file;
5. SHOULD write only the files whose bytes change, so that a version control system sees exactly what changed.

Reference: `writeFolder` in `server/eikSession.ts`; `EikMirror` in `src/core/versions/eikFolder.ts` (writes only records whose content hash changed).

A **library record** is a type, title block, detail family, image or other record that a writer takes from its own built-in library rather than from the model. A writer SHOULD NOT write a library record that no record of the model names. A record the model holds of its own, such as a type made for the project that no element uses yet, is the model's and is written.

*Note (informative).* Rule 2's SHOULD NOT was decided on 11 October 2026. Until Stomra 641 (live since 23 October 2026, `main` `83aa3e13`) the reference writer wrote its whole built-in library (about 250 types, 6 title blocks and a detail family in the version examined) into every folder, so a minimal model exported as about 270 files. It now leaves out a type of its library that is exactly as the library gives it and that nothing in the model names; a type a person made or changed is always written. Since Stomra 685 (live since 25 October 2026, `main` `9bd96356`) it leaves out a title block or a detail family of its library on the same terms; a sheet that names no title block names the generic frame of its paper, which is written. A title block an earlier build gave otherwise, never edited and unused, is still written. The examples in this repository were pruned of library records that nothing names; see `tools/build-example.mjs`.

## 14. Editing by hand

People and agents may edit record files with any tool.

1. Whoever changes a record by hand MUST add 1 to its revision (Part I, section 8).
2. After an edit, a reader that performs the whole-model check (section 12, rule 5) MUST perform it again before it accepts the folder, and MUST refuse a folder that no longer checks, naming the file and the rule. A reader that does not perform the check reads the edited folder as it is, and refuses it only for what section 12 lists. An agent host checks the folder whole after an edit, as Part VII, 6, rule 4 says.
3. A hand edit that sets a size a type sets (Part II, 5.3) makes a folder that fails the whole-model check: a validator reports it, and a reader that performs the check refuses the folder (rule 2).

Reference: `EikSession.sync` in `server/eikSession.ts` (re-reads edited files and checks the whole model; keeps the last good model while an edit is wrong); `typeSetInFiles`.

*Note (informative).* A live engine that keeps a folder open holds a lock file in `.eik/lock` naming its process, so that two engines do not write one folder. The lock is implementation state, not part of the format.
