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
types/<discipline>/<category>/<id>.json types
<discipline>/<level>/<category>/<id>.json elements: architecture, structure, systems, site
drawings/views/<kind>/<id>/view.json a view
drawings/views/<kind>/<id>/details/<n>.json its detail items, in chunks
drawings/sheets/<id>.json sheets (application records)
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.
Proposed for 0.2, section 4.4 puts each element, type, view and sheet in the folder of the discipline that owns it, one of eight: <discipline>/<level>/<category>/<id>.json, types/<discipline>/<category>/<id>.json, drawings/<discipline>/views/<kind>/<id>/view.json and drawings/<discipline>/sheets/<id>.json. A reader of 0.2 reads either layout.
- 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). - 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:
- Every
%is escaped as%25, first: the%of an escape is never escaped again. - 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%00to%1F. - 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.xwould otherwise be lost. - A trailing
.is escaped as%2E, and a trailing space as%20: some file systems drop them. - 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 isCON,PRN,AUX,NUL,COM0toCOM9,LPT0toLPT9, orCOMorLPTfollowed 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 idscon,Aux.1andlpt¹make the files%63on.json,%41ux.1.jsonand%6Cpt¹.json. - The hexadecimal digits of an escape are upper case.
- 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. - 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.
- 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
| Path | Holds | Schema |
|---|---|---|
eik.json | {format, units} | eik.schema.json |
AGENTS.md | text | none |
project/project.json | {schemaVersion, project, projectInformation?} | 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 |
types/<discipline>/<category>/<id>.json | a type | types/<category>.schema.json |
<discipline>/<level>/<category>/<id>.json | an element | elements/<category>.schema.json |
drawings/views/<kind>/<id>/view.json | a view without its detail items | drawings/view-record.schema.json |
drawings/views/<kind>/<id>/details/<n>.json | a chunk of detail items | drawings/view-details.schema.json |
drawings/sheets/<id>.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 |
In the owners' layout of section 4.4, proposed for 0.2, <discipline> is the record's owner, one of eight, and a view and a sheet are under their owner's folder: drawings/<discipline>/views/<kind>/<id>/view.json, its chunks beside it, and drawings/<discipline>/sheets/<id>.json, of the same depths below it.
Part XIII proposes the electrical records for 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 proposes the drawing files for 0.2, which are not 0.1's either: sheets as standard records at drawings/sheets/<id>.json, 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 proposes how a version and a store keep those assets: a pointer in the version's tree, and the bytes beside it.
- 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. - Every record file MUST validate against its schema.
schemas/index.jsonlists the same mapping for tools. An element or a type of a category that has no schema of its own here, one a later version adds, validates againstelements/element.schema.jsonortypes/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
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 |
- An element that carries
frameHost(a materialised framing member or truss) goes instructure. - A
componentelement goes instructurewhen it isstructural, or when its type is a plate (component.familyis"plate"), a cast-in item (insert) or a connector of the writer's own library; otherwise inarchitecture. Acomponenttype goes instructurewhen it is a plate, a cast-in item or a connector, otherwise inarchitecture.
Walls, slabs and roofs stay in architecture even when they bear: the folder follows who places them, not what they carry.
- A writer MUST place records by these rules.
- A reader MUST NOT derive a record's meaning from its folder. It MUST accept a
componentrecord in eitherarchitectureorstructure, since which components are connectors is the writer's library. - A
genericelement goes in the folder itsgeneric.disciplinenames (its own, else its type's), and inarchitecturewhen neither names one; agenerictype goes in the folder its owngeneric.disciplinenames, else inarchitecture(Part II, 6.6). A reader MUST accept agenericrecord in any discipline folder. - 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, proposed for 0.2, replaces this table and rules 1 and 2: there a record's folder is the discipline that owns it, one of eight, and rules 3 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/views/<kind>/<segment(id)>/view.json, wherekindis the view'skind.
In the owners' layout (section 4.4, proposed for 0.2), <discipline> is the record's owner, a view is at drawings/<owner>/views/<kind>/<segment(id)>/view.json and a sheet at drawings/<owner>/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).
- 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.txtof 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). - 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.jsonandarchitecture/level-0/wall/wall-a.jsonfold to one, and so doarchitecture/Level-1/wall/a.jsonandarchitecture/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 (proposed for 0.2)
This section is proposed for 0.2, with Part XVI. It writes open question 85, which the steward decided on 27 October 2026 for 0.2 ("85y"). It is not part of OpenEik 0.1: 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). @openeik/tools implements it when asked for the proposal ownership.
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.
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 anownerthat is none of the eight as an error;schemas/proposed/ownership/owner.schema.jsonstates this rule.An element's owner. An element that carries no
owneris owned, by the first of these that holds:structure: an element that carriesframeHost(a materialised framing member or truss); a wall, a slab or a roof that isstructural; a stair that isstructural, or that carries nostructuraland whose type'sstair.constructionisconcreteCast,concretePrecastorclt; an element of the categoriesbeam,column,foundation,slabEdge,rebarandmesh, andmember(Part II, 6.2.1, proposed for 0.2); and acomponentthat section 4.1, rule 2 puts instructurebecause it isstructuralor because its type is a plate or a cast-in item;ventilation: aduct, and aterminalthat is an air device (rule 3);plumbing: apipe, and every otherterminal;electrical: an element of the categorieselectrical,cableCarrierandcable(Part XIII);site: an element of the categoriesterrain,grading,propertyLine,planting,roadandparking;- a
genericelement:structureorsitewhere thegeneric.disciplinethat section 4.1, rule 5 reads (its own, else its type's) names it, elsearchitecture; - a
provision(Part II, 6.10, the proposalprovision): its asker's, the owner this rule gives the run itsprovision.forIdsnames first, elsesetup; architecture: every other element.
A
componentthat 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 arearchitecture's where it is not or where they have none (section 4.1, rule 6).An air device. A terminal is an air device when its type's
terminal.kindis a component in a duct (damper,fireDamper,smokeDamper,flowRegulator,silencerorductFan; Part II, 6.9),transferAirorftxUnit; when the kind isheatPumpand 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'smep.system, else its type'spipe.systemorduct.system, and a duct carries air where neither gives one. Any other terminal's system is its type'sterminal.system, else the system of its type's first connection. A terminal type is an air device by its kind, as above, or, where itsterminal.mountis notpipe, by its own system. A terminal without a type is no air device.A type's owner is its
owner. Else it isstructurefor 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 isstructuralor a stair as rule 2 says, and which carries noowner: the type's layers, or its flights, are the bearing build-up. Else it is the owner its category gives: aterminaltypeventilationwhen it is an air device (rule 3), elseplumbing; acomponenttypestructurewhen it is a plate or a cast-in item, else, as an element of its type, where it was read from (rule 2); agenerictype by its owngeneric.discipline, as rule 2 says; aprovisiontypearchitecture'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 totypes/structure/when a wall that uses it is madestructural, and back when none is.A view's and a sheet's owner. A view's owner is its
owner, elsesetup. A sheet's owner is that of its discipline code: itsdiscipline(Part XIV, 2.1), else the letters itsnumberbegins 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.AandBRarearchitecture's,Kstructure's,VandVEventilation's,VSplumbing's,Eelectrical's, andM,LandGthesite's; any other code, and none,setup's.sprinklerandsetupown an element or a type only by itsowner, but for a provision that names no run of the model, which issetup's (rule 2).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), anddocuments/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 fortypes/of each, witharchitecturewhere 4.1 says it.architecture,structureandsiteare folders of both layouts;systems,drawings/views/anddrawings/sheets/are 0.1's alone;ventilation,plumbing,electrical,sprinkler,setupanddrawings/<discipline>/the owners' alone, and Part I, 11.1, rule 2 reserves them.- an element:
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.
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. Its message says that it changes the layout. It SHOULD change no record but by moving it, so that its diff shows each record moved (Part VI, 4, rule 3); the rest of a model's migration to 0.2 (Part I, 9.6, rule 4) MAY be made in the same version. 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 rewritesproject/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).What changes, and what stays. The change of layout moves records and changes none. 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:
- UTF-8 without a byte-order mark, and a final newline.
- Object members sorted by name, comparing UTF-16 code units; members whose value is undefined are left out.
- 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{}. - 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. - Scalars are written as ECMAScript's
JSON.stringifywrites them: strings with its escapes, numbers in the shortest form that reads back as the same double,-0as0.
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.)
- 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.jsonoreik.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
- 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.
- A record's hash is the SHA-256 of the UTF-8 bytes of its canonical form, written as 64 lowercase hexadecimal digits.
- 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.
- 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.
- A kept file is a file at a plain relative path (no segment starting with
.or~, no\and no NUL) that is noteik.json, notAGENTS.mdand not at a record's path. Rule 8 keeps some plain paths from kept files too. - 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.
- 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.
- 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. - In the single-file form (section 11) kept files are the
extrasmember: text by path. - 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.
- 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 exampleacme/frame-a/). Part VI, 8, says how it keeps its results, with their result manifests, and its proposals there. - 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, witheik.jsonorAGENTS.md, whether or not the folder holds them, or with another kept file's path, nor pass througheik.jsonorAGENTS.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.jsonandAGENTS.mdfirst, 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 througheik.jsonorAGENTS.md. A validator MUST report each such file of a folder as a warning, and each such entry of theextrasof 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.
- A writer MUST write
AGENTS.mdwhen the folder has none, and MUST NOT overwrite one that exists: a person's edits to it stay. - 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
project/order.jsonmaps the name of each record list other thanelementsthat the model has (types,views,materials,sheetsand so on) to its ids in order.- A reader orders each list by it. Records it does not name follow, in path order; ids it names without a record are ignored.
- The order of elements is not part of a model. Implementations MUST NOT give it meaning.
- Levels keep their order in
project/levels.json. project/order.jsonis 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:
- When a view has detail items,
view.jsonholds the view without them, and the items go into chunk filesdetails/<n>.json, in order. A view with an empty list keeps"details": []inview.json. - 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 by0x01000193modulo 2³². This is not quite the FNV-1a of RFC 9923, which hashes octets: the two agree only for ids of ASCII characters. - 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, …. - 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:
{schemaVersion, project, projectInformation?, levels, grids?, scopeBoxes?, materials?, dimensionStyles?, detailFamilies?, titleBlocks?, images?, types, elements, views, sheets?, reports?, renders?, deviations?, areaAdjustments?, references?, pieces?, joints?, originals?, copies?, extras?}
- Lists hold their records in order; views hold their detail items inline;
extrasholds the kept files. schemas/model.schema.jsonvalidates it.- 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.jsonthat name no record list (section 9, rule 5) and the members ofeik.jsonthat a writer does not know (section 8.1), since the single-file form holds noeik.json. project/project.jsonholds the members of the single-file object other than its record lists,levelsandextras:schemaVersion,project,projectInformation, and any other member at the top of either, 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.jsonhas 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).
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.
11.1 The file extension and the media type
- 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.eikcomes before that, so that people and tools tell a model from other JSON. - Its media type is
application/vnd.openeik+json: JSON text (RFC 8259) in UTF-8, with the+jsonstructured 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 HTTPContent-Type, an upload, an attachment) SHOULD use it. - 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. - 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:
- reads
eik.jsonand checks the format (Part I, 9.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);
- parses each record file, and refuses a file that is not JSON, naming its path;
- assembles the model: the project, the levels, each list ordered by
project/order.json, each view joined with its chunks, the kept files; - 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.jsonis not JSON, has noformat, names a version it does not implement or givesunitsother than"mm", or that has noeik.jsonwhere Part I, 9.2, rule 5 does not let it read it asopeneik/0.1-draft(Part I, 3, rule 7, and 9.2); - a project whose
schemaVersionit does not implement (Part I, 9.3), or that givesunitsother than"mm"or anupAxisother 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.jsonor 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:
- writes every record at its path (section 4) in the stored form (section 5);
- writes every type that any record names, and SHOULD NOT write library records the model does not use (below);
- writes
eik.json, keeping the members of the folder's own that it does not know (section 8.1), andAGENTS.mdwhen there is none; - 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;
- 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.
- Whoever changes a record by hand MUST add 1 to its revision (Part I, section 8).
- 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.
- 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.