OpenEik.org

Draft OpenEik 0.2.0-draft: draft, not final. From commit 3a7fd749 of 31 October 2026. Errata: the errata of 0.2. Raw text: /spec/0.2/code-base.md. Earlier: OpenEik 0.1, kept as it was.

On this page

OpenEik 0.2 specification

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

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 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.

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

PathHoldsSchema
eik.json{format, units}eik.schema.json
AGENTS.mdtextnone
project/project.json{project, projectInformation?, schemaVersion?} (schemaVersion deprecated in 0.2, Part I, 9.3)project/project.schema.json
project/levels.jsonthe list of levelsproject/levels.schema.json
project/order.jsonlist name → idsproject/order.schema.json
project/grids/<id>.jsona gridproject/grid.schema.json
project/scope-boxes/<id>.jsona scope boxproject/scope-box.schema.json
project/materials/<id>.jsona materialproject/material.schema.json
project/dimension-styles/<id>.jsona dimension styleproject/dimension-style.schema.json
project/detail-families/<id>.jsona detail familyproject/detail-family.schema.json
project/title-blocks/<id>.json, project/images/<id>.jsonapplication recordsapplication-record.schema.json
project/originals/<id>.json, project/copies/<id>.jsonan original, a copyproject/original.schema.json, project/copy.schema.json
project/circuits/<id>.jsona circuit (Part XIII, 3)project/circuit.schema.json
project/people/<id>.jsona person or an agent of the registry (Part XIV, 5)project/person.schema.json
types/<discipline>/<category>/<id>.jsona typetypes/<category>.schema.json
<discipline>/<level>/<category>/<id>.jsonan elementelements/<category>.schema.json
drawings/<discipline>/views/<kind>/<id>/view.jsona view without its detail itemsdrawings/view-record.schema.json
drawings/<discipline>/views/<kind>/<id>/details/<n>.jsona chunk of detail itemsdrawings/view-details.schema.json
drawings/<discipline>/sheets/<id>.jsona sheet (Part XIV, 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>.jsonapplication recordsapplication-record.schema.json
fabrication/pieces/<id>.json, fabrication/joints/<id>.jsona piece, a jointfabrication/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 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 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 says how a version and a store keep those assets: a pointer in the version's tree, and the bytes beside it. Part XVI adds project/owners.json (project/owners.schema.json), and Part XVIII 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 lists the same mapping for tools, and schemas/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:

DisciplineCategories
structurebeam, column, foundation, slabEdge, rebar, mesh
systemspipe, duct, terminal
siteterrain, grading, propertyLine
architectureevery 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.

  1. A writer of 0.1 MUST place records by these rules, and a writer of 0.2 by section 4.4.
  2. 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.
  3. 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.
  4. 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.

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).

  1. The fold of a name is the name in Normalization Form D (UAX #15, 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 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.

4.4 Folders that follow owners

Part of 0.2, with Part XVI. 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 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); 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);
    • 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): 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), 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.

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:

{
  "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.)

  1. 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: 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.

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).

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.

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), with the template of section 11.2.

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.

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.

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.

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.