# Part XIV. Drawings

*This part is a part of OpenEik 0.2, by the steward's decisions of 15 October 2026 that sheets and issued drawings join OpenEik in 0.2, and of 16 October 2026 that it be written then, as Part XIII was. It binds every conformance class it names, its own two among them, the drawing validator and the drawing issuer (section 19; [Conformance](https://openeik.org/spec/0.2/conformance)). OpenEik 0.1 had it as a proposal, which no class of 0.1 required: a reader of 0.1 keeps the files of this part as section 1.5 says file by file, sheets as the application records they are in 0.1 (Part II, 9.1), the part's other files as kept files (Part III, 7) where they are UTF-8 text of at most 1 MiB, leaving larger or binary files alone and never removing them (Part III, 7, rule 2), and the new members of detail items as unknown members (Part I, 10, rule 1). A reader of 0.2 reads a model of 0.1 by 0.1's rules (Part I, 9.6, rule 3). `@openeik/tools` applies this part to every model of 0.2, and to a model of 0.1 when asked (`proposals: ["drawings"]`, `openeik check --proposal drawings`); it works out freshness and verifies sign-offs with `openeik drawing verify`, `status`, `impact`, `diff` and `register` (17.3).*

## 1. Status and scope

This part states how a model's drawings are issued and checked. A sheet is a record: its paper, the views placed on it and its title-block fields. A drawing issued from it is a snapshot in a restricted SVG, whose lines derived from the model name the elements that drew them, kept in the folder with a record of what it was drawn from and of every record it read. So a change to the model shows, by hashes and without drawing anything, which issued drawings it makes stale. Around the issued drawings are the project's own codes, a register of every revision, the issues that send them, the reviews that answer them and the sign-offs that approve them.

This part follows the steward's decision of 15 October 2026, which approved the ten decisions of the drawings plan, D1 to D10, with two refinements: signing is optional and scales with the stage, and clouds are chosen for each update. This part cites those decisions by number:

- **D1** sheets and issued drawings join the standard;
- **D2** the current issued revision is kept in the model's folder;
- **D3** OpenEik defines neutral states, and the project its own codes;
- **D4** a sign-off is one statement, signed at one of two strengths: an everyday signature, or a legal one;
- **D5** drawings are the headline of 0.2;
- **D6** a PDF/A-3 rendition at every issue;
- **D7** the AI rule: agents propose, and a person accepts;
- **D8** outside drawings are in the same register;
- **D9** the words of section 1.3;
- **D10** the roadmap says so now.

**Status.** The rules of this part bind every implementation of 0.2 of a class they name (section 19). They are numbered, and cited as `XIV.<section>.<rule>`: XIV.6.12.3 is section 6.12, rule 3. A validator reports a breach of a MUST as an error and a breach of a SHOULD as a warning (section 19). The part's schemas are among those of 0.2 (`schemas/0.2/drawings/`, the SVG profile's allowlist among them, and `project/person.schema.json`), and its conformance cases in the suite of 0.2.

### 1.1 What this part covers

- **sheets** as standard records: paper, viewports and title-block fields (section 2; D1);
- **annotations**: the references of detail items declared as references, with provenance and anchors, and the AI rule (section 3; D7);
- **the project's drawing profile**: its codes mapped onto OpenEik's neutral states (section 4; D3);
- **people and keys**: the registry that names everyone this part names, with the keys of those who sign (section 5);
- **the issued drawing**: a restricted SVG, "OpenEik drawing SVG 0.1", whose derived lines name their elements (section 6);
- **issued files and where they live**: `drawings/issued/<drawing>/`, kept in the model's folder as a deliberate exception to "derived data is never stored" (section 7; D2);
- **the revision record**, **the read set** and the exact **freshness** rule (sections 8 and 9);
- **change items and revision clouds**, chosen for each update (section 10);
- **the register**, **issues** (transmittals), **reviews** and **sign-offs** (sections 11 to 14; D4, D8, D9);
- **the PDF/A-3 rendition** (section 15; D6);
- what is derived and never stored (section 16).

### 1.2 What it leaves out

- **Title-block graphics:** the frame, the logo and the layout of the block are the application's (D1); the fields are data (2.3).
- **Drawing style:** line weights, hatch patterns, lettering and symbols are the application's or its profile's (ISO 128 and ISO 3098 are defaults an application may claim). This part fixes only what makes a drawing safe, exact, traceable and reproducible.
- **How a generator draws:** the hidden-line method is the generator's. This part asks only that output is deterministic, that derived lines name their elements and that the read set is complete.
- **National code lists:** status, purpose and decision codes are the project's, in its drawing profile, as plain values (Part II, 8, rules 3 and 4; D3). OpenEik lists none.
- **What a host does:** viewers, comparison views, notifications, delivery and read receipts, seen ticks, the page that answers a printed code. A host keeps those; this part's files never depend on them (7.2).
- **Raster images** in views, but the picture of a 3D view (6.3); DWG and DXF renditions; and IFC drawings.
- **Legal acceptance** of a signed drawing: whether a signature binds in law is not this part's to answer.

### 1.3 Words

This part uses these words in a sense of its own. A later version may move them into [Terminology and references](https://openeik.org/spec/0.2/terminology-and-references).

| Word | Meaning |
|---|---|
| view | the recipe for one drawing region (Part II, 10): a record since 0.1 |
| sheet | a paper: its size, its viewports and its title-block fields; a standard record from this part on |
| drawing | a numbered document, one sheet in this part, whose number is stable across revisions |
| revision | one issued state of a drawing: its SVG, its records and its sign-offs. Immutable. Identified by its `sequence`; shown by its revision code |
| snapshot | a revision as written: taken deliberately, as a draft, a shared sketch or an issue, and never changed |
| issue | a release of revisions to recipients for a purpose: a transmittal. A BCF item is a *topic*, as BCF calls it |
| derivation | what a revision was drawn from: the version, the sheet and views, the generator and its fonts, the read set by hash |
| read set | every record a revision's drawing read, with its hash, and the elements its views' volumes held |
| stale | a revision whose read set no longer matches the line of work it tracks, or into one of whose views an element entered |
| drawing profile | the project's drawing conventions, `drawings/profile.json`: codes, revision series, cloud rules, required sign-offs, fonts. It is not a profile of Part II, 8, nor the SVG profile |
| SVG profile | OpenEik drawing SVG 0.1: the subset of SVG an issued drawing is written in (section 6) |
| orphan | an annotation whose anchors no longer resolve: drawn, listed, never dropped |
| candidate | a drawing drawn again for a check, from a later version or a proposal: never stored |
| unlinked drawing | an outside drawing, such as a consultant's PDF, registered and issued like any other, never source-mapped |
| drawing file | a JSON file of this part that is not a record: revision records, read sets, registers, issues, reviews, sign-offs, the drawing profile |

### 1.4 Where its files live

| File | Path | Kind | Section |
|---|---|---|---|
| a sheet | `drawings/<discipline>/sheets/<id>.json` | record (list `sheets`; `drawings/sheets/<id>.json` in 0.1's layout) | 2 |
| the provenance and anchors of detail items | in the view's chunks, `drawings/<discipline>/views/<kind>/<id>/details/<n>.json` | members of records | 3 |
| the drawing profile | `drawings/profile.json` | drawing file | 4 |
| a person or an agent, with its keys | `project/people/<id>.json` | record (list `people`) | 5 |
| the current issued drawing | `drawings/issued/<drawing>/drawing.svg` | issued file | 6, 7 |
| its revision record | `drawings/issued/<drawing>/revision.json` | drawing file | 8 |
| its read set | `drawings/issued/<drawing>/read-set.json` | drawing file | 9 |
| its sign-offs | `drawings/issued/<drawing>/signoffs/<n>.json` | drawing file | 14 |
| the register of a drawing | `drawings/register/<drawing>.json` | drawing file, append-only | 11 |
| an issue (transmittal) | `drawings/issues/<issue>.json` | drawing file | 12 |
| a review round | `drawings/reviews/<review>.json` | drawing file | 13 |
| renditions, outside drawings, fonts, legal containers | `assets/<sha256>.<ext>` | asset, by digest | 7.3, 15 |
| previews and caches | `.eik/drawings/` | hidden: never part of the model | 16 |

`<drawing>` is the path segment (Part III, 2) of the drawing's number; `<id>`, `<issue>`, `<review>` and `<n>` are path segments of ids. In the owners' layout of Part III, 4.4, the layout of 0.2, a sheet is at `drawings/<discipline>/sheets/<id>.json` and a view under `drawings/<discipline>/views/`, `<discipline>` its owner; the files of this part's own, `drawings/profile.json`, `drawings/issued/`, `drawings/register/`, `drawings/issues/` and `drawings/reviews/`, keep their place, and none of their names is a discipline's.

[Part XVIII](https://openeik.org/spec/0.2/review), written beside this part from plan 688, adds a model's own reviews (`project/reviews/`), the members of an issue that release a version and name its prints (12.1), an issue's statements and receipts (`drawings/issues/<issue>/`) and terms accepted (`project/terms-accepted/`).

### 1.5 How it fits into 0.1

| What | What a 0.1 reader does |
|---|---|
| `drawings/sheets/<id>.json` | keeps it as an application record (Part II, 9.1): checked for an `id` only, and written back unchanged |
| `project/people/<id>.json` | keeps it as a kept file: it is no record path of 0.1 (Part III, 3, rule 1) |
| the JSON drawing files | keeps each as a kept file while it is text of at most 1 MiB, and leaves a larger one alone: a read set of a very large sheet may pass 1 MiB |
| `drawing.svg` | keeps it as a kept file at 1 MiB or less (A-101's 891 kB at P01 is one), and leaves a larger one alone, so that no 0.1 version holds it (Security and privacy, 5.3, rule 2). A reader of 0.2 holds issued files of up to 64 MiB (7.3) |
| `assets/<sha256>.<ext>` | binary: left alone and never removed; no 0.1 version holds it, and git carries it |
| `provenance` and `anchors` in a detail item | unknown members, kept at any depth (Part I, 10, rule 1), about which a 0.1 validator may warn (Part I, 10, rule 2) |
| the single-file form | `sheets` is a list of 0.1 already; `people` is an unknown member at the top, which a 0.1 reader writes into `project/project.json` (Part III, 11, rule 4), as it writes Part XIII's circuits; the drawing files travel in `extras` while they are kept files |

A host or a tool that keeps a folder in git SHOULD mark `drawings/issued/**` and `assets/**` as binary (`-text`), so that no conversion of line ends changes a signed file.

*Note (informative).* `drawings` and `project` are folder names that 0.1 gives, and `assets` one that it reserves (Part I, 11.1, rule 2). A writer of 0.2 writes the standard's own files into the standard's own folders, which is what the reservation keeps them for, as Part XIII's note says of `project/circuits/`.

### 1.6 The parts it extends

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

- Part II: the sheet record (1, and a new section beside views), the person record, the references of detail items (7) with their `provenance` and `anchors`; the documentation profile (8) gains sheets;
- Part III: the paths (1 and 3), the issued file as a file kind of its own (up to 64 MiB, never rewritten), assets by digest, and the lists `sheets` and `people` of the single-file form (11);
- Part IV: drawings stay derived (1, rule 1), with issued drawings as the stated exception to rule 2 (section 16);
- Part V: `sheet.upsert` and `sheet.delete` into the documentation profile (2.2), `person.upsert`, and provenance on the detail commands (section 17);
- Part VI: digests that include detail chunks (8.3); issued files in versions; the register and history (section 11);
- Part VII: an operation that answers which issued drawings a version or a proposal makes stale (17.4);
- Part VIII: the IFC mapping of issued drawings (section 18);
- Conformance: the classes *drawing issuer* and *drawing validator* (section 19);
- Security and privacy: 5.4 ("No signatures in 0.1") replaced by this part's sign-offs; the SVG profile's safety; personal data in the person registry; agents' instructions by digest.

*Note (informative).* The reference implementation issues drawings in this part's files since Stomra 566 (17 October 2026), which took its issuer, its work 555, off its flag (`src/core/drawings/stomraIssue.ts`; section 5's note). Its sheets are still application records of 0.1, whose members 2.3's note maps onto this part's, and the SVG its screen draws is its own markup, which the SVG profile refuses (6.12, the note).

*Note (informative).* A prototype of this part, built on the reference implementation at `37e8f202`, issued the four sheets of the reference implementation's L-house on 15 October 2026, moved one window, worked out which drawings that made stale without drawing anything, and issued the stale sheets again with clouds. The examples of this part carry its values (section 2), and 9.6 gives its measurements.

## 2. Sheets

A sheet becomes a standard record (D1). Its paper and its viewports are those of the reference implementation's sheet (see the reference in 2.2); its title-block fields are this part's (2.3), and the reference implementation's other members map onto them as 2.3's note says, which is to be agreed with it.

The examples of this part are drawings of the L-house, the reference implementation's test model, whose records `examples/l-house` holds (130 elements and 13 views), with the four sheets that the reference implementation gives it and that folder leaves out. The prototype (section 1.6, the note) issued the sheet A-101 ("Plans, facades and roof section", A1, 841 × 594 mm) at P01, with the house as `examples/l-house` holds it, and at P02, after the window `study-window-east` had moved 500 mm along its host wall `study-wall-1`. Most examples show the east facade: the viewport `study-viewport-4`, which places the view `view-east` at 1 : 75. They carry the prototype's real values. Where a value cannot come from the prototype, a hash that this part defines otherwise than the prototype did, it is a placeholder string `"<…>"`, so that every example is valid JSON. Members are in the order of the stored form (Part III, 5), and short objects are written on one line to save room.

### 2.1 The record

`drawings/<discipline>/sheets/<id>.json` holds one sheet (Part III, 4.4):

| Member | | Type and range | Meaning |
|---|---|---|---|
| `id` | required | Part I, 6 | its id |
| `number` | required | string, 1 to 80 characters | the drawing number, ISO 7200's identification number. It names the folder of its issued drawing, and is stable across revisions (2.4, rule 7) |
| `name` | required | string, 1 to 200 characters | the title |
| `discipline` | optional | string, 1 to 4 capital letters | the sheet's discipline code: the letters a drawing number of its discipline begins with, `A`, `K`, `V`, `VS`, `E` or a project's own. Where it gives none, the letters its `number` begins with are its code. Its folder in the owners' layout of Part III, 4.4 is the code's discipline's (rule 5 there) |
| `revision` | required | Part I, 8 | the record's revision, a precondition token; not the drawing's revision code |
| `width`, `height` | required | number, more than 0, at most 10 000 | the paper, in mm |
| `viewports` | required | list, at most 200 (2.2) | the views placed on it |
| `fields` | optional | object (2.3) | the values of the title-block fields |
| `titleBlockId` | optional | string, or `none` | the application's title block (Part II, 9.1) whose graphics it uses; `none`, a blank sheet |
| `inRegister` | optional | boolean | `false` leaves the sheet out of the register a host lists; absent, it is listed |

1. **A sheet of 0.1.** In 0.1 a sheet is an application record of its writer's (Part II, 9.1). A record of `sheets` in a model of 0.2 that does not give every member the table requires, `number`, `name`, `revision`, `width`, `height` and `viewports`, is a sheet of 0.1 kept as an application record, as the migration to 0.2 leaves it (Part I, 9.8, rule 5). It is held to `application-record.schema.json` alone, a reader keeps it unchanged (Part II, 9.1, rule 2), no drawing is issued on it, and no other rule of this part applies to it. A validator SHOULD warn about it (`sheet-application-record`). A writer of 0.2 writes a sheet as the table says.

### 2.2 Viewports

A viewport is `{id, viewId, x, y, detailNumber, rotation?, title?}`:

| Member | | Meaning |
|---|---|---|
| `id` | required | its id, unique in the sheet (2.4, rule 3) |
| `viewId` | required | the view placed (`x-eik-ref` `view`) |
| `x`, `y` | required | the top-left corner of the viewport's box on the paper, in mm, y down. The box's size is the view's `crop` at the view's `scale` |
| `detailNumber` | required | 1 to 40 characters: the number or the name its title and marks show, `5` or `GRUNDPLAN` |
| `rotation` | optional | from −360 to 360 degrees, about the box's centre |
| `title` | optional | `{show?, lines?, width?, offset?}`: whether the viewport's title shows, its lines, its width and its offset |

Reference: `sheetSchema` and `sheetViewportSchema` in Stomra's `src/core/sheets/documentation.ts` (at `37e8f202`), where a viewport's title is `showTitle`, `titleLines`, `titleWidth` and `titleOffset`, which `title` gathers; `src/components/documents/SheetContent.tsx` places a viewport's box by its top-left corner, y down.

### 2.3 Title-block fields

The printed block is drawn from data. ISO 7200's eight mandatory fields, with its revision index and document status, come from:

| ISO 7200 field | From |
|---|---|
| identification number | the sheet's `number` |
| title | the sheet's `name` |
| date of issue | the revision record's `date` (8.1) |
| revision index | the revision record's `revision` (8.2) |
| document status | the revision record's `status`, printed where the drawing profile says (4.1) |
| legal owner, creator, document type, approval person | `fields.legalOwner`, `fields.creator`, `fields.documentType`, `fields.approver` |
| sheet number | `fields.sheetNumber`; absent, 1 (a drawing is one sheet in this part) |

`fields` holds those four or five as strings of 1 to 200 characters, the optional `supplementaryTitle`, `responsibleDepartment`, `technicalReference` and `language`, and extension members (Part I, 11.2). ISO 7200 allows the creator and the approver to be held outside the printed block, which the sign-offs do (section 14): `fields.approver` is what is printed, not proof of approval.

The values that change with each revision (the date, the revision code, the status, the rows of the revision table) come from the revision record and the register, never from the sheet, so that issuing changes no record.

*Note (informative).* So the prototype's sheet record of A-101 kept its hash from P01 to P02. The reference implementation's sheet holds more members than this part, and they map so, as is to be agreed with it:

| The reference implementation's sheet | Here |
|---|---|
| `id`, `number`, `name`, `revision`, `width`, `height`, `titleBlockId`, `inRegister`, and the viewports' `id`, `viewId`, `x`, `y`, `detailNumber` and `rotation` | as they are |
| `author`, and the `parameters` keyed `sheet.drawnBy`, `sheet.checkedBy`, `sheet.designedBy`, `sheet.approvedBy` and `sheet.responsible` | `fields`: `creator`, `approver`, and extension members for the others |
| `parameters["sheet.issueDate"]`, `revisions` | the revision record and the register |
| `status` | the revision record's `status` |
| `fontFamily` | one family of the drawing profile's font set (4.1) |
| `references`, `scaleBar`, `registerGroup` | extension members under the reference implementation's prefix, unless it asks for them in the standard |
| `images` | an extension member: the pictures placed on the sheet, which its issued drawing draws in its sheet layer (6.3) |

A-101 of the L-house, its fields as the prototype's neutral values would give them (abridged: A-101 has six viewports):

```json
{
  "fields": {
    "creator": "<creator>",
    "documentType": "Plans, facades and section",
    "legalOwner": "<legal owner>"
  },
  "height": 594,
  "id": "roof-study-sheet",
  "name": "Plans, facades and roof section",
  "number": "A-101",
  "revision": 0,
  "titleBlockId": "generic-A1",
  "viewports": [
    {
      "detailNumber": "5",
      "id": "study-viewport-4",
      "viewId": "view-east",
      "x": 290,
      "y": 270
    }
  ],
  "width": 841
}
```

### 2.4 Rules

1. A sheet's `number` MUST be unique among the model's sheets, and the path segments of two sheets' numbers MUST NOT fold to one (Part III, 4.3): each names a folder of `drawings/issued/`.
2. A sheet's `number` SHOULD match the drawing profile's `numbering`, where it gives one.
3. Each viewport's `viewId` MUST name a view of the model, and each viewport's `id` MUST be unique in its sheet.
4. A viewport's `detailNumber` SHOULD be unique in its sheet. It SHOULD be short: a number, or the word a sheet names its view by where the profile's practice names views, as Swedish sheets often do (`BJÄLKLAGSPLAN`).
5. `titleBlockId`, unless `none`, SHOULD name a title block of the model.
6. The members MUST have the types and ranges of 2.1 to 2.3. The schema states this rule.
7. A drawing's number is stable. A writer MUST NOT change the `number` of a sheet that has an issued drawing unless that drawing's register ends in `withdrawn` (11.1): a renumbered sheet is a new drawing. A validator reports an issued drawing whose sheet (by `derivation.sheet.id`) now carries another number while its register does not end in `withdrawn`.

## 3. Annotations: references, provenance and anchors

Detail items (Part II, 10; their schema, `drawings/detail-item.schema.json`) stay as 0.1 has them. This part declares their references, adds who made each item, and says what an issued drawing does with an item whose element is gone (D7).

A dependent view (`dependentOn`) draws its primary's detail items, as the reference implementation's `src/core/views/viewInheritance.ts` resolves a view, followed through each `dependentOn` to a view that depends on none. Where this part speaks of the detail items or the detail chunks of a viewport's view (3.4, rule 4; 6.12, rule 23; 9.1; 9.4, step 3; 9.7, rule 3), a dependent view's are its primary's.

### 3.1 References

These members name records, and get `x-eik-ref` (Part I, 7, rule 4); each SHOULD resolve (Part I, 7, rule 3):

| Member | Names (`x-eik-ref`) |
|---|---|
| `host.elementId`, `hatchHost.elementId`, `tag.elementId`, `floorList.slabId` | `element` |
| `hatchHost.layerId` | a layer of the element's type (checked in code) |
| `dimensionReferences[].reference.elementId` (of the kinds `face`, `layer`, `axis`, `edge` and `opening`) | `element` |
| `dimensionReferences[].reference.gridId` | `grid` |
| `dimensionReferences[].reference.levelId`, `floorList.levelId` | `level` |
| `dimensionReferences[].reference.detailId` | `detail`: an item of the same view (a new value) |
| `viewReference.viewId` | `view` |
| `partsList.pieceId` | `piece` (a new value) |

In the live model, deleting an element still deletes the items that name it (Part V, 5, rule 3); an issued drawing is a snapshot, and keeps them. An item's reference can still fail to resolve after a merge or a hand edit: the item is then an orphan (3.3), kept and reported.

### 3.2 Provenance and the AI rule

A detail item MAY carry `provenance`, and an item an agent writes MUST carry it:

| Member | | Meaning |
|---|---|---|
| `by` | required | `person` or `agent` |
| `person` | optional | for `person`: who drew it, a person of the registry (section 5) |
| `agent` | required for `agent` | `{name, version}`: the agent, its name 1 to 120 characters and its version 1 to 80 |
| `actingFor` | required for `agent` | the person it acts for |
| `instruction` | required for `agent` | `{digest, ref?}`: the SHA-256 of the instruction, and an opaque reference to it under access control; never the instruction's text, so that what a client asked stays out of the model |
| `made` | required for `agent` | `{version, root?}`: the version of the model it saw, named as Part VI, 8.2's `computedFrom.version` names one |
| `status` | required for `agent` | `proposed` until a person accepts the item, then `accepted` |
| `acceptedBy`, `acceptedAt` | required when `accepted` | the person who accepted it, and when (RFC 3339, in UTC) |

The AI rule (D7): agents propose through commands, with provenance; a person accepts every agent's item; no sheet is issued while it holds a proposed item (3.4, rule 4); and an agent never signs an approval (14.5). Issued SVGs keep each item's origin (`data-eik-origin`, 6.8), so that the agents' share stays visible.

An insulation item that an agent drew in a section, accepted:

```json
{
  "a": [1200, 0],
  "b": [1200, 2400],
  "color": "#555555",
  "id": "det-90",
  "insulationKind": "zigzag",
  "kind": "insulation",
  "provenance": {
    "acceptedAt": "2026-10-16T09:20:00Z",
    "acceptedBy": "person-k2m9xa",
    "actingFor": "person-k2m9xa",
    "agent": {"name": "<agent>", "version": "<version>"},
    "by": "agent",
    "instruction": {"digest": "<sha256 of the instruction>"},
    "made": {"version": "<model version id>"},
    "status": "accepted"
  }
}
```

### 3.3 Anchors and orphans

An annotation's references are its anchors, tried in order. Each anchor resolves to one of four states, worked out when the drawing is drawn and recorded only in the issued SVG (`data-eik-anchor`, 6.8):

- `attached`: the element and its named feature exist; the position is kept in the feature's own frame, so that the note moves with the wall;
- `moved`: found through a recorded successor (a split, a replacement), or by a scored geometric match above the drawing profile's `reanchor.threshold`;
- `elementOnly`: the element exists, and the feature does not;
- `orphaned`: drawn at its own last place in the view, in an orphan style, its derived values shown as "?", listed for a person to resolve, and attached again by itself if the element returns.

The members that hold a feature (`layer:<id>`, `face:<name>`, `edge:<id>`; never an index) and a position in the feature's frame, the anchors of a detail item, are to be fixed with the reference implementation's work on anchors. Successors need the lineage that commands record, which 0.2 has not specified: until it does, `moved` is found by geometry only.

### 3.4 Rules

1. The references of 3.1 SHOULD resolve. A validator reports one that does not, and the item is kept: it is an orphan.
2. An item whose `provenance.by` is `agent` MUST give `agent`, `actingFor`, `instruction.digest`, `made.version` and `status`. Its schema states this rule.
3. `status` `accepted` MUST give `acceptedBy` and `acceptedAt`, and `acceptedBy`, `actingFor` and `person` MUST name people of the registry (5.2, rule 2).
4. A sheet MUST NOT be issued while a view placed on it holds an item whose `provenance.status` is `proposed`. A validator reports it for an issued drawing whose views' detail chunks are unchanged since the issue (their pairs in the read set equal the model's).
5. `provenance.instruction` MUST NOT hold the instruction's text: only `digest` and `ref`. Its schema states this rule.
6. An issuer MUST NOT leave an annotation out of an issued drawing because its anchors fail: it draws it orphaned (6.12, rule 27).

## 4. The project's drawing profile

OpenEik defines neutral states; a project declares its codes (D3). Each model has one drawing profile, `drawings/profile.json`. It is a drawing file, not a record: tools write it, people may edit it, and every drawing that prints from it reads it (its pair is in the `sheet` list of every read set, 9.2).

### 4.1 The file

| Member | | Type | Meaning |
|---|---|---|---|
| `format` | required | `"openeik-drawing-profile/0.1"` | |
| `id` | required | string, 1 to 80 characters, a plain value | the profile's name, as revision records cite it: `example`, `iso19650-uk-2021`, `beast-2.1` |
| `statuses` | required | list, 1 to 100 | `{code, name?, state, sharedFor?, printed, revisions, clouds, requires?}`: a status code; its name as people read it, a plain string ("For review and comment", "Granskningshandling"), which a tool shows first, with the code after it; the OpenEik state it means (8.2); for `shared`, `information` or `review`; whether the sheet prints it; the revision series it uses (8.2); whether clouds print by default (`on` or `off`); and the sign-offs it needs, `[{action, role?}]` (section 14) |
| `purposes` | optional | list | `{code, name?}`: the purposes of issue (in Sweden, the *handling*) |
| `decisions` | optional | list | `{code, gate, outcome, name?}`: decision codes. `gate` is `A` (approve for sharing) or `B` (authorise and accept); `outcome` is `accept`, `acceptWithComments` or `reject` |
| `reviewStates` | optional | list | `{code, open, name?}`: the states of review items, and whether each leaves an item open |
| `newRevisionOnStatusChange` | optional | boolean | whether a change of status alone takes a new revision code (in Sweden it does; UK guidance says not); absent, false |
| `numbering` | optional | string | a regular expression (ECMAScript, with the `u` flag) that drawing numbers match |
| `fonts` | required where the folder holds a linked issued drawing (4.3, rule 5) | list, 1 to 20 | `{family, sha256, file?}`: the declared, pinned font set: a family's name as the drawings write it, the SHA-256 of the font file that draws it (which may be a free stand-in for that family), and the asset that holds the file (`assets/<sha256>.ttf` or `.otf`) |
| `keepEarlierClouds` | optional | boolean | whether a revision keeps the clouds of the revisions before it; absent, false |
| `reanchor` | optional | object | `{threshold}`, from 0 to 1: the score below which a moved anchor fails (3.3) |
| `owners` | optional | object | `{<drawing number>: <person id>}`: who is responsible for each drawing, so that a host can route its reviews |

Every code is a plain value: OpenEik lists none, and checks none against a code list (Part II, 8, rules 3 and 4). The schema marks them `"x-eik-values": "national"`.

The prototype's `example` scheme, made a profile (its codes follow ISO 19650's UK annex, as an illustration only):

```json
{
  "decisions": [
    {"code": "A1", "gate": "B", "outcome": "accept"},
    {"code": "B1", "gate": "B", "outcome": "acceptWithComments"},
    {"code": "approved", "gate": "A", "outcome": "accept"},
    {"code": "rejected", "gate": "A", "outcome": "reject"}
  ],
  "fonts": [
    {"family": "<family>", "file": "assets/<sha256>.ttf", "sha256": "<sha256>"}
  ],
  "format": "openeik-drawing-profile/0.1",
  "id": "example",
  "numbering": "^A-[0-9]{3}$",
  "reviewStates": [
    {"code": "actedOn", "open": false},
    {"code": "elsewhere", "open": false},
    {"code": "needsNewReview", "open": true},
    {"code": "new", "open": true},
    {"code": "rejected", "open": false},
    {"code": "toActOn", "open": true}
  ],
  "statuses": [
    {"clouds": "off", "code": "S0", "name": "Initial status", "printed": false, "revisions": "P.minor", "state": "draft"},
    {"clouds": "off", "code": "S2", "name": "Suitable for information", "printed": false, "revisions": "P", "sharedFor": "information", "state": "shared"},
    {"clouds": "off", "code": "S3", "name": "Suitable for review and comment", "printed": false, "revisions": "P", "sharedFor": "review", "state": "shared"},
    {"clouds": "on", "code": "A1", "name": "Authorised and accepted", "printed": true, "requires": [{"action": "approved", "role": "approver"}], "revisions": "C", "state": "published"},
    {"clouds": "off", "code": "CR", "name": "As constructed record", "printed": true, "revisions": "C", "state": "asBuilt"}
  ]
}
```

### 4.2 How common schemes map onto the states (informative)

| OpenEik state | ISO 19650 (UK annex) | Sweden (BEAst Namnruta 2.1) | Sign-offs, by default | Clouds, by default |
|---|---|---|---|---|
| live | no container: nothing is stored | none | none | none |
| `draft` | WIP, S0; P01.01 | Preliminär | none | none |
| `shared`, `information` | S2; P01 | Information | none | off |
| `shared`, `review` | S3 (S1, S4, S6, S7 as the profile says); P01 | Granskning: dated, no letter | optional | off, switchable |
| `published` | A1…An, B1…Bn; C01 | Godkänd with its handling; letters from tender or construction issue | as the profile and the contract require | on in Sweden, switchable |
| `asBuilt` | CR | Relationshandling | as required | removed |
| superseded, withdrawn | none | none | none | none |

Superseded and withdrawn are not statuses: the register records them (section 11). OpenEik ships no national profile. The Swedish default is the authoring tool's default for a Swedish project (D3), never a list in the standard.

### 4.3 Rules

1. The file MUST validate against its schema.
2. Codes MUST be unique within `statuses`, `purposes`, `decisions` and `reviewStates`, and families within `fonts`.
3. A folder that holds an issued drawing MUST hold `drawings/profile.json`.
4. Each font SHOULD be in the folder, at `assets/<sha256>.<ext>`. A font the project may not store is named by its digest alone; then no one else can draw the sheet again or check its rendering. The issued file stays verifiable by its digest.
5. A profile MUST give `fonts` where the folder holds a linked issued drawing. A profile that only gives a model's reviews their codes (Part XVIII, 2) needs none (open question 67).

## 5. People and keys

Every file of this part names people: reviewers, recipients, authors and signers. Each is named by the id of a record of one registry in the folder, of people and agents, which also holds the keys of those who sign. No file of this part names a person by an email address (Security and privacy, 2.3, rule 1 already prefers ids).

### 5.1 The record

`project/people/<id>.json` holds one person or agent; the list is `people`:

| Member | | Type and range | Meaning |
|---|---|---|---|
| `id` | required | Part I, 6; it MUST NOT hold `@` | the person's id, `person-k2m9xa`, minted without coordination (Part I, 6, rule 5); never an email address |
| `revision` | required | Part I, 8 | |
| `kind` | required | `person` or `agent` | |
| `name` | optional | string, 1 to 120 characters | the name printed or shown: personal data, kept in every version that holds it (Security and privacy, 2.4) |
| `organisation` | optional | string, 1 to 120 characters | |
| `roles` | optional | list of plain values | the roles the project gives the person (`approver`, `checker`) |
| `agent` | required for `agent` | `{name, version?}` | the software |
| `keys` | optional | list, at most 20 | the keys this person or agent signs with |

A key is `{keyid, algorithm, public, validFrom, validUntil?, rpId?}`: `keyid`, 1 to 80 characters, unique in the registry; `algorithm`, `ed25519` or `ecdsa-p256`; `public`, the public key, the base64 of its DER SubjectPublicKeyInfo; `validFrom` and `validUntil`, RFC 3339 times in UTC; and `rpId`, for a passkey, the WebAuthn relying party it was registered with. A passkey's algorithm is `ecdsa-p256` (ES256). A key is revoked by giving it a `validUntil`, never by removing it: a signature made before then, with a time-stamp to prove it, stays valid.

Who may add a key is the project's to decide: the registry changes as any record does, through review and a person's merge. A host's own verified keys may seed it; the folder never depends on the host to check one.

```json
{
  "id": "person-k2m9xa",
  "keys": [
    {"algorithm": "ed25519", "keyid": "k-7f3a9c", "public": "<base64 SPKI>", "validFrom": "2026-10-01T00:00:00Z"}
  ],
  "kind": "person",
  "revision": 0,
  "roles": ["approver"]
}
```

*Note (informative).* Applications that sign people in through one shared account name a person alike: `person-` and the first 12 lowercase hexadecimal digits of the SHA-256 of the account id's UTF-8 bytes, as the account service gives the id (`personIdForAccount` in `@openeik/tools`). So a person whom the authoring tool and a host both write into a building is one record of its registry, whichever wrote it first. The reference implementation has written its issuers so since Stomra 566 (`personOf` in `src/core/drawings/stomraIssue.ts`). An id written in another form stays, as Part I, 6, rule 3 asks, and two records of one person are not merged.

### 5.2 Rules

1. A person record MUST validate against its schema.
2. Every member of this part's files that names a person MUST name a record of `project/people/`: the drawing profile's `owners`; provenance's `person`, `actingFor` and `acceptedBy`; a revision's `by`; a register's `issued.by` and `withdrawn.by`; an issue's `sent.by` and recipients' `person`; a review's `opened.by`, `closed.by`, reviewers' `person`, items' and replies' `author.person`, `author.actingFor`, `author.acceptedBy` and history's `by`; a statement's `signer`.
3. A member of this part's files that names a person, and a person's `id`, MUST NOT hold an email address: people are named by id.
4. A `keyid` MUST be unique in the registry, and a key's `validUntil` MUST NOT be before its `validFrom`.
5. A key MUST NOT be removed once a sign-off names it; a writer ends it with `validUntil`.

## 6. The issued drawing: OpenEik drawing SVG 0.1

An issued drawing is written in OpenEik drawing SVG 0.1, the SVG profile: the static subset of SVG 1.1 that the renderers tested drew alike, with SVG 2's `href` and `data-*` attributes. Its safety comes from an allowlist and a validator, not from sanitising. The allowlist is published once, as data, in `schemas/0.2/drawings/svg-profile.json`, which a validator and any host's parser share.

### 6.1 Bytes

The file is UTF-8 without a byte-order mark, with LF line ends, and holds no XML declaration, DOCTYPE, entity declaration, comment, processing instruction or CDATA section. It is its own Canonical XML 1.1 form (without comments), followed by one LF. The canonical form is a lint, never a step of verifying a signature, which covers the exact bytes. It has one element per line: each start tag begins a line; an element with element children ends on a line of its own; and an element with only text is one line.

### 6.2 Paper and scale

The root's `width` and `height` are the sheet's paper, in `mm`, and its `viewBox` is `0 0 W H` with the same numbers: one user unit is one millimetre of paper. A viewport is a group whose `transform` is the matrix from the view's own coordinates (u along, v up, in model mm) to the paper: the derivation's `matrix` (6.9), written `matrix(a b c d e f)`. The paper is white: a renderer draws an issued drawing on white, and the file need not draw the paper. A file whose sheet layer comes after the viewport groups (6.7) draws no white over its views, so it shows the paper only through what it leaves empty.

### 6.3 Elements

Allowed: `svg` (the root only), `g`, `defs`, `title`, `desc`, `metadata`, `path`, `rect`, `circle`, `ellipse`, `line`, `polyline`, `polygon`, `text`, `tspan`, `use`, `pattern`, `clipPath` and `marker`, and `image` only as a picture, below: a 3D view's, or one of the sheet layer. Forbidden: every other element, among them `script`, `foreignObject`, `style`, `image` anywhere else, `symbol`, `filter` and its primitives, `mask`, gradients, animation elements, `a`, `switch`, a nested `svg`, and any element outside the SVG namespace.

**Pictures of 3D views.** By the steward's decision of 16 October 2026, the `derived` layer of the viewport group of a view of kind `3d` MAY hold the view's picture in place of line work: one `image`, and nothing else. Its `href` is a `data:image/png;base64,` URI, never a file or a URL, so that the picture fetches nothing; it carries `data-eik-role` `picture` and no `data-eik-id`; its `x`, `y`, `width` and `height` place it in the viewport group's coordinates; and it counts towards the file's 64 MiB (6.5). A picture is not source-mapped: a review anchors to its place on the sheet, a comparison compares its bytes, and its viewport's volume is the whole model (9.3).

*Note (informative).* The reference implementation draws 3D views as pictures. Without them, a sheet that places a 3D view could not be issued in this profile until 3D views are drawn as lines.

**Pictures on the sheet.** By the steward's decision of 19 October 2026, the `sheet` layer MAY hold pictures: a title block's logo, or a picture placed on the sheet. Each is an `image`, at any depth of the layer and any number of them, whose `href` is a `data:image/png;base64,` or `data:image/jpeg;base64,` URI, never a file or a URL. It carries `data-eik-role` `picture` and no `data-eik-id`, `data-eik-record` or `data-eik-item`; its `x`, `y`, `width` and `height` place it in paper mm, and its `preserveAspectRatio`, where it has one, is `none`, `xMidYMid meet` or `xMidYMid slice`; it counts towards the file's 64 MiB (6.5). Like a 3D view's, it is not source-mapped: a review anchors to its place on the sheet, and a comparison compares its bytes. Where the picture is a record of the model's (a project's image), the sheet's read set holds that record (9.1).

*Note (informative).* Without them, a sheet whose title block prints a firm's logo, as most do, could not be issued in this profile. The picture is the application's graphics (1.2): the profile only lets it be carried, safely.

### 6.4 Attributes and values

- On any element: `id`, `class`, `role`, `aria-label`, `transform` (not on the root), `clip-path`, `opacity`, `fill`, `fill-opacity`, `fill-rule`, `stroke`, `stroke-width`, `stroke-opacity`, `stroke-linecap`, `stroke-linejoin`, `stroke-miterlimit`, `stroke-dasharray`, `stroke-dashoffset`, `marker-start`, `marker-mid`, `marker-end`, `font-family`, `font-size`, `font-weight`, `font-style`, `text-anchor`, and the `data-eik-*` attributes of 6.8.
- By element: on the root, `xmlns` (SVG's), `width`, `height` and `viewBox`; on `rect`, `x`, `y`, `width`, `height`, `rx` and `ry`; on `circle`, `cx`, `cy` and `r`; on `ellipse`, `cx`, `cy`, `rx` and `ry`; on `line`, `x1`, `y1`, `x2` and `y2`; on `polyline` and `polygon`, `points`; on `path`, `d`; on `text` and `tspan`, `x`, `y`, `dx`, `dy` and `xml:space="preserve"`; on `title` and `desc`, `lang`; on `use`, `href`, `x` and `y`; on `image`, `href`, `x`, `y`, `width`, `height` and `preserveAspectRatio` (6.3); on `pattern`, `x`, `y`, `width`, `height`, `patternUnits`, `patternContentUnits`, `patternTransform` and `viewBox`; on `clipPath`, `clipPathUnits`; on `marker`, `markerWidth`, `markerHeight`, `markerUnits`, `refX`, `refY`, `orient` (`auto` or an angle) and `viewBox`; on `metadata`, `id`.
- On any element too, an attribute `data-<prefix>-<name>` under a prefix registered to an application (Part I, 11.1), which every reader ignores: an application keeps its own markers so, and this part names none of them.
- Forbidden: `on*`, `style`, `vector-effect`, `xlink:*`, `xml:base`, `display`, `visibility`, `pointer-events`, any other `data-*` attribute, and any attribute in another namespace.
- Colours: `none`, `#rrggbb` in lower case, or `url(#<id>)` of a `pattern`.
- Transforms: `matrix`, `translate`, `scale` and `rotate` only.
- Numbers: fixed decimals, with no exponent, no `+`, no leading zeros and no trailing zeros after the point, and `-0` written `0`; at most three decimals, except the six numbers of a viewport group's `matrix`, which have at most nine. No unit anywhere but the root's `mm`.

*Note (informative).* A scale of 1 : 75 cannot be written in three decimals, so a viewport's matrix has nine.

### 6.5 References and limits

`href` is only on `use`, as `#<id>`, and on a picture, a 3D view's or one of the sheet layer, as 6.3 says; `url(#<id>)` is only in `clip-path`, `marker-*`, `fill` and `stroke`; and each names an element of the file, of the right kind. A `use` never reaches itself or an ancestor, directly or through other `use`s, and expanding the file's `use`s adds at most 1 000 000 elements. The file is at most 64 MiB, with at most 2 000 000 elements and a depth of at most 32.

### 6.6 Text and fonts

`font-family` names exactly one family of the drawing profile's font set, with no list of fallbacks: in tests, engines fell back silently, and each differently. Text stays `<text>`, so that people, `grep` and agents read it; outlining text is a rendition's option, never the issued file's.

### 6.7 Layers and order

The root's children are, in this order: `title`, never empty, which SHOULD read `<number> <revision> <title>`; `desc` (optional); `metadata` (6.9); `defs` (optional); the sheet layer, `<g data-eik-layer="sheet">`, with the border, the title block, the revision table and the optional code of 6.8, all drawn from records; one viewport group per viewport, in the order of the sheet's `viewports`; and, where there are clouds, `<g data-eik-layer="revisions">`, in paper millimetres. By the steward's decision of 17 October 2026, the sheet layer MAY instead come after the viewport groups, and before the `revisions` layer, so that its frame lies over the views' fills. The root holds one sheet layer, in one place or the other. Inside a viewport group are the layers `derived`, `drafted` and `agent`, in that order. Inside a layer, its children are in the order that the derivation's `generator.order` names (6.9): `id`, sorted by id (`data-eik-id`, `data-eik-item`); or `paint`, back to front as the generator paints them, ids breaking ties, for a generator whose sections and elevations rely on the order of painting until it draws exact hidden lines. Every value of an `id` attribute occurs once in the file (6.12, rule 14). Nothing in the file is volatile: no time but the revision's date, no id generated at random, no banner of the generator. The ids that `use`, `clip-path` and markers need are made from what they belong to (`clip-study-viewport-4`).

**Parts.** By the steward's decision of 17 October 2026, where the derivation's `generator.order` is `paint`, an element MAY have several groups in a viewport's `derived` layer: its parts, in the order the generator paints them, with other elements' groups and parts between them. Each part carries `data-eik-id`, naming the element (or, for a shared path, its elements) as a group does; `data-eik-part`, its place among the element's parts in the layer, 0, 1, 2, … in the order painted, which is the order of the file; and its own `data-eik-hash` (6.8). An element drawn in one group needs no `data-eik-part`. Where `generator.order` is `id`, nothing changes: an element has one group in a layer, and nothing carries `data-eik-part`.

*Note (informative).* Why parts: a generator that paints piece by piece, its fills and lines interleaved, cannot give each element one group without drawing differently. The reference implementation paints its plans, sections and elevations so, each element's pieces where their height or depth puts them, its white faces hiding what lies behind them. Held to one group per element, its issuer ordered whole elements by what covers what; where two elements hid each other in places, one of them had to go first as a whole, and the issued drawing differed from the screen there. With parts, an issued drawing paints in the generator's own order, and its hashes still compare element by element, part by part (9.4, step 5; 10.3, rule 6).

### 6.8 The model link

| Attribute | On | Says |
|---|---|---|
| `data-eik-layer` | layer groups | `sheet`, `derived`, `drafted`, `agent` or `revisions` |
| `data-eik-viewport`, `data-eik-view` | viewport groups | the sheet's viewport, and the view placed there |
| `data-eik-id` | each element's group in a `derived` layer, and each of its parts (6.7); a path shared by several elements | the element, or the elements (an element of a placed copy by its derived name, `<copy id>/<element id>`, Part I, 6, rule 6), space-separated in ascending order, that produced it |
| `data-eik-part` | each part of an element in a `derived` layer, where `generator.order` is `paint` (6.7) | the part's place among the element's parts in that layer, in the order painted: `0`, `1`, `2`, …, a whole number with no leading zero |
| `data-eik-record` | a group of derived line work of a record that is no element; a record's line work, like the annotations the generator makes, may come in several groups, which are no parts and carry no `data-eik-part` | `<list>/<id>`: `grids/grid-a`, `levels/level-0`, `views/view-section` (a section mark) |
| `data-eik-role` | drawn primitives inside an element's group, and a picture (6.3) | `cut`, `poche`, `projection`, `hidden`, `overhead`, `beyond`, `hatch`, `symbol`, `text`, or `picture` on a picture (6.3) |
| `data-eik-feature` | drawn primitives, where the generator can name the feature | `layer:<id>`, `face:<name>`, `edge:<id>`: a named feature, never an index |
| `data-eik-hash` | each element's group, each part, and each shared path | the SHA-256, as 64 lowercase hexadecimal digits, of the group's canonical bytes as the file holds them, its own `data-eik-hash` attribute left out: a part's, of the part alone, its `data-eik-part` included. The generator snaps line work to a grid of 0.01 mm of paper, in the viewport's coordinates, so that unchanged elements keep their hash and a moved viewport changes none |
| `data-eik-item` | each annotation's group in `drafted` and `agent` | the detail item's id |
| `data-eik-ref` | annotation groups | the elements the item names, space-separated |
| `data-eik-origin` | annotation groups | `derived`, `person` or `agent`; `derived` for the annotations that the generator makes from the view's settings, which sit in the `derived` layer |
| `data-eik-anchor` | annotation groups | `attached`, `moved`, `elementOnly` or `orphaned` (3.3); absent, `attached` |
| `data-eik-rev`, `data-eik-change` | clouds and revision marks in the `revisions` layer | the revision code, and the change items marked, space-separated |
| `data-eik-code` | the optional printed code in the sheet layer | the digest it encodes: the derivation's hash (6.9) |

*Note (informative).* Why `data-eik-*` attributes, and not `id` or a namespace: they read the same under XML and HTML parsing, they survive optimisers, which deleted or renamed plain ids in tests, and they need no bookkeeping of namespaces in canonical XML. The reference implementation's screen markup names elements with `data-element-id`, `data-detail-id` and `data-viewport-id`, and marks clouds with `<g data-revision>`, `data-cloud` and `data-elements`; this part's names replace them. The grid of 0.01 mm is the prototype's, which compared numbers to 0.01 mm; the conformance vectors are to confirm it. The printed code encodes the derivation's hash because a file cannot hold its own digest, nor the revision record's, which names the SVG's; the register indexes the derivation's hash (11.1).

### 6.9 The derivation

`<metadata id="openeik-derivation">` holds the derivation, as the text of its canonical JSON (RFC 8785). It never holds the SVG's own digest, nor the revision record's, which names the SVG's: a file cannot contain its own digest. The same object is the revision record's `derivation` (8.1), and its hash (Part III, 6) is the derivation's digest.

| Member | | Meaning |
|---|---|---|
| `format` | required | `"openeik-drawing-derivation/0.1"` |
| `drawing`, `revision`, `title` | required | the number; the revision code as printed, 0 to 20 characters, empty where the profile prints none; the title |
| `source` | required | `{version, versionKind, root, ref, upstream}`: `version` and `versionKind`, the version drawn from, named as Part VI, 8.2's `computedFrom` names one (`null` for a folder under no version control); `root`, the model's root tree hash as read (Part VI, 2, rule 4), always given; `ref`, the line of work the drawing tracks (`main`, `alternatives/<slug>`, a git branch), or `null`; and `upstream`, as Part VI, 8.2 has it |
| `sheet` | required | `{id, hash}` |
| `views` | required | `{<view id>: <hash>}`: the hash of each placed view with its detail items joined, as the single-file form holds it |
| `viewports` | required | `[{id, view, kind, detailNumber, place {x, y, width, height, rotation}, scale, crop, matrix}]`, in the sheet's order: the view's kind (`plan`, `section`, `elevation`, `3d` or `drafting`), where each sits on the paper (in mm), its scale (the 75 of 1 : 75), its crop, and the matrix `[a, b, c, d, e, f]` from view to paper, `x = a u + c v + e`, `y = b u + d v + f`. A 3D view's picture has no scale and no crop: both are `null`, and its matrix places the picture's box on the paper |
| `generator` | required | `{name, version, order, styles?, request?}`: the generator; its version (a release or a commit); the order inside its layers (`id` or `paint`, 6.7); the digest of its style set; and the request it was given |
| `fonts` | required | `{<family>: <sha256>}`: each family the file uses |
| `readSet` | required | `{hash, count}`: the read set file's hash (Part III, 6), and its number of distinct pairs |
| `annotations` | optional | `{derived, person, agent}`: how many annotation groups of each origin the file holds |

A-101 at P02, abridged to one viewport (the prototype's values, reshaped):

```json
{
  "drawing": "A-101",
  "fonts": {"<family>": "<sha256>"},
  "format": "openeik-drawing-derivation/0.1",
  "generator": {
    "name": "Stomra",
    "order": "paint",
    "request": {"action": "snapshot_2d", "format": "svg", "viewId": "roof-study-sheet"},
    "version": "37e8f202"
  },
  "readSet": {"count": 165, "hash": "<hash of read-set.json>"},
  "revision": "P02",
  "sheet": {"hash": "615ca99ca792f6a421651c275e43fd16abf330dc4cd5b86235644db92196f30d", "id": "roof-study-sheet"},
  "source": {
    "ref": null,
    "root": "6b572fb1e20adea6f4d012a31588f32aef0dc7995796d2630497fb3f74e1f378",
    "upstream": null,
    "version": null,
    "versionKind": null
  },
  "title": "Plans, facades and roof section",
  "viewports": [
    {
      "crop": [-4200, 13500, -700, 6500],
      "detailNumber": "5",
      "id": "study-viewport-4",
      "kind": "elevation",
      "matrix": [0.013333333, 0, 0, -0.013333333, 346, 356.666666667],
      "place": {"height": 96, "rotation": 0, "width": 236, "x": 290, "y": 270},
      "scale": 75,
      "view": "view-east"
    }
  ],
  "views": {"view-east": "f44967a88a44b26fbee6dc50eca8aea9a0c495a20fe34df07a2b975d17561287"}
}
```

*Note (informative).* The prototype's model folder was under no version control, so `version` is `null` and its tree's root is `root`. Its derivation also held a `context` and the digests of each viewport's inputs, which this part moves into the read set (section 9). The prototype's A-101 read 162 distinct records; with the drawing profile and the detail chunks of its two annotated views, `count` is 165.

### 6.10 The file, in outline

In canonical form, with its attributes sorted and empty elements written as start and end tags:

```xml
<svg xmlns="http://www.w3.org/2000/svg" height="594mm" viewBox="0 0 841 594" width="841mm">
<title>A-101 P02 Plans, facades and roof section</title>
<metadata id="openeik-derivation">{"drawing":"A-101",…}</metadata>
<g data-eik-layer="sheet">…</g>
<g clip-path="url(#clip-study-viewport-4)" data-eik-view="view-east" data-eik-viewport="study-viewport-4" transform="matrix(0.013333333 0 0 -0.013333333 346 356.666666667)">
<g data-eik-layer="derived">
<g data-eik-hash="…" data-eik-id="study-wall-1">
<path d="…" data-eik-role="projection"></path>
</g>
<g data-eik-hash="…" data-eik-id="study-window-east">
<path d="…" data-eik-role="projection"></path>
</g>
</g>
<g data-eik-layer="drafted"></g>
</g>
<g data-eik-layer="revisions">
<path d="…" data-eik-change="chg-1" data-eik-rev="P02" data-eik-viewport="study-viewport-4" fill="none" stroke="#c0392b" stroke-width="0.5"></path>
</g>
</svg>
```

The `defs` with `clip-study-viewport-4`, and the other five viewports, are left out.

Where `generator.order` is `paint`, an element painted in two pieces, with another element painted between them, is two parts (6.7): here the wall's face, then the window in it, then the wall's lines over both.

```xml
<g data-eik-layer="derived">
<g data-eik-hash="…" data-eik-id="study-wall-1" data-eik-part="0">
<path d="…" data-eik-role="projection" fill="#ffffff"></path>
</g>
<g data-eik-hash="…" data-eik-id="study-window-east">
<path d="…" data-eik-role="projection"></path>
</g>
<g data-eik-hash="…" data-eik-id="study-wall-1" data-eik-part="1">
<path d="…" data-eik-role="projection"></path>
</g>
</g>
```

### 6.11 What a reader may ignore, and how a host shows it

A renderer draws the file as static SVG, and MAY ignore `metadata`, `title`, `desc`, `role`, `aria-label`, `class` and every `data-*` attribute. A reader of the model link MAY ignore the `sheet` and `revisions` layers. A reader MUST NOT fetch, run or follow anything outside the file. A host SHOULD serve an issued SVG as `image/svg+xml`, as an image, with a strict content security policy and `nosniff`, and never inline in a page that holds the viewer's credentials: this makes Security and privacy, 11, rule 4 concrete.

### 6.12 Rules

1. An issued SVG MUST be UTF-8 without a byte-order mark, with no CR and no NUL.
2. It MUST hold no XML declaration, DOCTYPE, entity declaration, comment, processing instruction or CDATA section.
3. It MUST be its own Canonical XML 1.1 form, without comments, followed by one LF.
4. It SHOULD hold one element per line, as 6.1 says.
5. The root MUST be `svg`, in the SVG namespace, and that MUST be the only namespace declared.
6. Its `width` and `height` MUST be the paper of the sheet the derivation names, in `mm`, and its `viewBox` MUST be `0 0 W H` with the same numbers.
7. Its first child MUST be a `title` that is not empty.
8. It MUST hold only the elements 6.3 allows.
9. Each element MUST hold only the attributes 6.4 allows it.
10. Colours, transforms, `orient` and units MUST be as 6.4 says.
11. Numbers MUST be as 6.4 says.
12. Every `href` and `url()` MUST name an element of the file, of the kind 6.5 says, and nothing outside it; the `href` of a 3D view's picture MUST be a `data:image/png;base64,` URI, and of a picture of the sheet layer a `data:image/png;base64,` or `data:image/jpeg;base64,` URI (6.3).
13. A `use` MUST NOT recurse, and expanding the file's `use`s MUST add at most 1 000 000 elements.
14. Ids MUST be unique in the file.
15. The file MUST be at most 64 MiB, with at most 2 000 000 elements and a depth of at most 32.
16. Every `font-family` MUST name one family of the drawing profile's font set, and the derivation's `fonts` MUST name each family the file uses with the set's digest.
17. The root's children, the viewport groups and their layers MUST be in the order 6.7 says, with one sheet layer, before the viewport groups or after them, and the viewport groups MUST be the derivation's viewports.
18. Where the derivation's `generator.order` is `id`, the children of each layer MUST be sorted by id.
19. Every drawn primitive of a `derived` layer, but the picture of a 3D view (6.3) and the annotation groups that the generator makes from the view's settings (6.8), MUST have exactly one ancestor-or-self carrying `data-eik-id` or `data-eik-record`.
20. A `derived` layer MUST hold at most one group per element, or, where the derivation's `generator.order` is `paint`, one per element and part. Where an element has parts in a layer, all its groups there MUST be parts, their `data-eik-part` 0, 1, 2, … in the order of the file; and `data-eik-part` MUST NOT be on anything but a part (6.7).
21. Each id of `data-eik-id` MUST name an element of the model at the source version.
22. Every element's group, and every shared path, MUST carry `data-eik-hash`, equal to the hash 6.8 defines.
23. Each annotation group MUST carry `data-eik-origin` agreeing with its layer (`drafted` holds `person`, `agent` holds `agent`, `derived` holds `derived`), and each annotation group of the `drafted` and `agent` layers MUST carry `data-eik-item` naming an item of its viewport's view (of a dependent view, its primary's: section 3): an annotation the generator makes from the view's settings is no item.
24. `data-eik-viewport` and `data-eik-view` MUST agree with the derivation and the sheet.
25. The file MUST hold one `metadata`, `id` `openeik-derivation`, whose text is the derivation in canonical JSON, valid against its schema, with no member that depends on the file's own bytes.
26. An element's group SHOULD NOT lie wholly outside its viewport's crop: a generator leaves out what the view does not show.
27. An annotation whose anchors fail MUST be drawn with `data-eik-anchor` `orphaned`, never left out, and `data-eik-anchor` MUST be one of the states of 3.3.

*Note (informative).* The prototype's A-102 drew five doors outside its crop (rule 26). The prototype's SVGs are the reference implementation's screen markup at `37e8f202`, with the derivation added: one line, not canonical, with nested `svg` elements (seven in A-101), `style` attributes, `pointer-events`, `overflow`, colours such as `white` and `#fff`, ids that the screen's framework generates afresh, no `title`, a `metadata` with a `data-format` attribute and JSON that is not canonical, white underlays with no element's id, and several groups of one element in one viewport, none of them a part. They fail rules 3, 4, 7, 8, 9, 10, 19, 20 and 25.

## 7. Issued files and the folder

### 7.1 Layout

`drawings/issued/<drawing>/` holds the latest snapshot of each drawing, of any state but live: `drawing.svg`, `revision.json`, `read-set.json` and `signoffs/<n>.json`. An unlinked drawing's folder holds `revision.json` and `signoffs/` only, its file being an asset. `signoffs/` holds the sign-offs of every revision of the drawing, the current one's and earlier ones', and none is ever removed: each names the revision it signs by digest (section 14). Earlier revisions stay in the history, and the register (section 11) finds each by its digests and the version it was drawn from: a checkout does not carry every revision, and nothing issued is lost. Issued SVGs are plain, uncompressed text, so that they diff and merge: in tests, ten revisions of a sheet of 7 MB and 100 000 paths packed to 1.68 MiB in git, against 13 MiB as compressed SVG.

### 7.2 Drawing files

Every JSON file of this part that is not a record:

- has a `format`, `openeik-drawing-<kind>/0.1`, by which a validator chooses its schema. A reader MUST NOT interpret a file whose `format` it does not implement. The prototype's ids that end in `/0.1-proposal` name other shapes, and are not read as these formats;
- is written in the stored form (Part III, 5), and its **hash** is the SHA-256 of its canonical form (Part III, 6); an SVG's, a PDF's or a font's digest is the SHA-256 of its bytes. Both are 64 lowercase hexadecimal digits, with no `sha256:` prefix;
- writes times as RFC 3339, in UTC with `Z`, and dates as `YYYY-MM-DD`;
- is named, where a read set lists it (the drawing profile), by the hash of its leaf in a version tree, as Part VI, 8.3 names a kept file: the hash of its text. Everywhere else this part names a drawing file by the hash above. The two differ, and each has its use: a read set is compared with a tree's leaves without parsing anything;
- is **complete without a host**: it holds no host's URL and no id that only a host knows, names people only by ids of the registry (5.2, rules 2 and 3), and holds no email address.

### 7.3 Assets

`assets/<sha256>.<ext>` holds a binary file by the SHA-256 of its bytes, of one of the kinds of [Part XV, 4](https://openeik.org/spec/0.2/assets#4): this part's are a PDF rendition or an outside drawing (`pdf`), a font (`ttf`, `otf`) and a container of the legal tier (`asice`), and the images a review item attaches (`png`, `jpg`; 13.2). An asset is never changed. A host may keep the bytes outside the version tree, as stores of large files do, and the folder names them by digest. Part III, 7's limits of 1 MiB and of text do not hold for assets, nor for issued drawings and read sets of up to 64 MiB: a reader and a version store of 0.2 hold them in its versions. Part XV says how a version, a store, a push and a pull hold assets.

*Note (informative).* A 0.1 reader of versions refuses a version that holds a file which a reader of a folder leaves alone, one over 1 MiB or holding NUL (Security and privacy, 5.3, rule 2). So a version that holds an asset, or an issued file over 1 MiB, is read only by readers of 0.2, and by readers of 0.1 that apply the proposals `drawings` or `assets`.

### 7.4 Rules

1. A drawing's folder SHOULD hold only the files of 7.1.
2. A drawing's folder's name, read back (Part III, 2, rule 7), MUST equal its revision record's `drawing`.
3. Each drawing file MUST be in the stored form and validate against the schema of its `format`.
4. An issued drawing's files are written once: a writer MUST NOT replace them except with a later revision of the same drawing, all together (`drawing.svg`, `revision.json` and `read-set.json`; the sign-offs stay), and MUST NOT remove them in any other case. A withdrawn drawing keeps its last revision.
5. An asset is a file of `assets/` as Part XV, 2 says: its name the SHA-256 of its bytes and the extension of one of the kinds of Part XV, 4. A file of `assets/` that is not one is no asset (Part XV, 2, rule 3).
6. Every folder of `drawings/issued/` MUST have a register, and every register its folder.

## 8. The revision record

`drawings/issued/<drawing>/revision.json` says what the revision is. The derivation is in it, and the SVG's digest beside it; the sign-offs' statements sign both (section 14).

### 8.1 Members

| Member | | Type and range | Meaning |
|---|---|---|---|
| `format` | required | `"openeik-drawing-revision/0.1"` | |
| `drawing` | required | string, 1 to 80 characters | the drawing's number |
| `sequence` | required | integer, at least 1 | the revision's place in the drawing's series: its identity |
| `revision` | required | string, 0 to 20 characters | the revision code shown (P02, C01, B), empty where the profile shows none |
| `title` | required | string, 1 to 200 characters | |
| `date` | required | date | the date of issue |
| `state` | required | `draft`, `shared`, `published` or `asBuilt` | the OpenEik state (8.2) |
| `sharedFor` | required when `shared` | `information` or `review` | |
| `status` | required | `{profile, code, purpose?}` | the profile's `id`, its status code, and its purpose code where the project uses one |
| `kind` | optional | `linked` or `unlinked` | absent, `linked` |
| `derivation` | required for `linked` | object (6.9) | equal to the SVG's |
| `svg` | required for `linked` | `{file, sha256, bytes}` | `file` is `drawing.svg` |
| `file` | required for `unlinked` | `{asset, sha256, bytes, mediaType}` | the outside drawing, an asset (8.3) |
| `changes` | optional | list (10.1) | what changed since the revision it supersedes |
| `clouds` | required | `on`, `off` or `throughout` | the choice made for this update (10.2) |
| `supersedes` | required when `sequence` is more than 1 | `{sequence, revision, record, svg?}` | the revision it replaces: its sequence, its code, its revision record's hash and its SVG's digest |
| `by` | optional; SHOULD be given for `shared`, `published` and `asBuilt` (8.5, rule 12) | person id | who issued it, a person of the registry (section 5) |

*Note (informative).* The prototype's revision record held the read set inline (`inputs` and `volume`, keyed by id) and the SVG as `file`; this part keeps the read set in a file of its own, keyed by path (section 9), and names the SVG `svg`.

### 8.2 States, statuses and revision codes

- **States** are OpenEik's (D3). `draft`, `shared` (for information or for review), `published` and `asBuilt` are a revision's; superseded and withdrawn are the register's (section 11); live is never stored.
- **Statuses** are the project's: `status.code` is a code of the drawing profile, which says its state and whether the sheet prints it.
- **Revision codes** are worked out from the series that the profile gives the status: `none` (dated only, as a Swedish preliminary or review issue), `P` (P01, P02, …), `P.minor` (P01.01, …, for work in progress), `C` (C01, …), `letters` (A to Z, then AA, AB, …, leaving out I and O, as ISO 7200 asks) and `numbers` (1, 2, …). The register keeps the `sequence`; the code shown is the profile's rendering of it. Where the profile's `newRevisionOnStatusChange` is false, a change of status alone keeps the code.
- **A change of status alone** MAY be issued as a new revision record over the same `drawing.svg`, whose bytes then do not change, where the profile neither prints the status nor takes a new revision code on a change of status (`newRevisionOnStatusChange` false, as in UK practice, where a status is metadata). Its `derivation`, its `revision` code and its read set are then the superseded revision's, the read set with the `sequence` of the revision that drew the file. So what was reviewed is what is issued, through a change of status too.

### 8.3 Unlinked drawings

An outside drawing, such as a consultant's PDF, is registered, reviewed and issued as any other (D8). Its file is an asset (`assets/<sha256>.pdf`); its revision record has the `kind` `unlinked` and `file`, and no `svg`, `derivation` or read set; and its folder holds no `drawing.svg`. It is never shown as source-mapped, and its freshness is not worked out. A conversion to SVG for viewing is derived, and not stored.

### 8.4 Example

A-101 at P02, as the prototype issued it (`derivation` abridged: it is the whole object of 6.9):

```json
{
  "changes": [
    {
      "by": "person",
      "elements": ["study-wall-1", "study-window-east"],
      "id": "chg-1",
      "viewports": ["study-viewport-0", "study-viewport-1", "study-viewport-4"],
      "what": "East window moved 500 mm"
    }
  ],
  "clouds": "on",
  "date": "2026-10-15",
  "derivation": {"drawing": "A-101", "format": "openeik-drawing-derivation/0.1", "revision": "P02"},
  "drawing": "A-101",
  "format": "openeik-drawing-revision/0.1",
  "revision": "P02",
  "sequence": 2,
  "sharedFor": "review",
  "state": "shared",
  "status": {"code": "S3", "profile": "example"},
  "supersedes": {
    "record": "<hash of P01's revision.json>",
    "revision": "P01",
    "sequence": 1,
    "svg": "742dadf029a3a787dd9589bc1ba00f3d1e0fc157b0584246e81d08e1e7db2efe"
  },
  "svg": {"bytes": 900524, "file": "drawing.svg", "sha256": "7e7e0bbe28f97181127a5e2b7f624004ae6848f9ae322eb81d68c82c018fa05c"},
  "title": "Plans, facades and roof section"
}
```

### 8.5 Rules

1. The record MUST validate against its schema.
2. `drawing`, `revision` and `title` MUST equal the derivation's, and `drawing` the number of the sheet the derivation names.
3. `svg.sha256` and `svg.bytes` MUST equal `drawing.svg`'s.
4. `derivation` MUST equal the derivation in `drawing.svg`, member for member.
5. `derivation.readSet.hash` MUST equal `read-set.json`'s hash, and `count` its number of distinct pairs.
6. `sequence` MUST be 1 for a drawing's first revision, else its superseded revision's plus 1; `supersedes` MUST be absent exactly when `sequence` is 1.
7. `status.profile` MUST be the drawing profile's `id`; `status.code` MUST be a code of its `statuses`, whose `state` and `sharedFor` are the record's; `status.purpose`, where given, MUST be a code of its `purposes`.
8. `revision` SHOULD be the code the profile's series gives for its status and sequence.
9. A change of status alone MAY keep the superseded revision's `drawing.svg`, `derivation`, revision code and read set, where 8.2 allows it.
10. `date` SHOULD NOT be earlier than the superseded revision's.
11. An unlinked revision MUST have `file` and no `svg`, `derivation` or read set, and its folder MUST hold no `drawing.svg` or `read-set.json`.
12. A revision of state `shared`, `published` or `asBuilt` SHOULD give `by`, the person who issued it: who issued a drawing is among the first things a reviewer asks.

## 9. The read set and freshness

Staleness is computed, not guessed. A revision records every record its drawing read, with its hash, and the elements its views' volumes held; one look-up of a hash per record and one spatial query per view then tell whether it is still current, without drawing anything. A false alarm is acceptable; a miss is not.

### 9.1 What a drawing reads

A drawing reads every record that the generator consulted, not only the elements it drew: for each viewport, its view and every detail chunk of it, each element drawn, its type and its type's materials, its host, the elements joined to it, the levels, the scope box, and the views whose marks it draws; for the sheet, the sheet record, its title block, the project record, the levels, the drawing profile, the people whose names it prints, and every sheet that its reference list names: where it lists or counts the project's sheets (a drawing register, a reference list of the other sheets, a sheet count), every sheet record. So does a sheet whose views mark a view placed on no sheet, since a mark prints the number of the sheet its view is placed on, and any sheet may come to place it. A register that prints its sheets' scales and revisions also reads the views placed on them, with their detail chunks. The prototype showed why: the wall's record never changed when its window moved, yet the wall's drawing changed in four viewports, which only the host reference predicted. A read set MAY hold more than the generator read: a pair it did not need costs only a false alarm, never a miss, but one each time it changes. The order of the lists, `project/order.json`, is an index and no record (Part III, 9), and every type, view or other record added changes it: a sheet's list holds it only where the order of the model's sheets decides something the sheet prints, or where the sheet reads every sheet record and the model holds no other sheet. A sheet added enters no list there (9.4, step 2), and the order index is the one record it changes. What it MUST hold at least is 9.7, rules 3 and 4, say; a generator that cannot report what it read lists every record of its viewports' volumes.

### 9.2 The file

`drawings/issued/<drawing>/read-set.json`:

| Member | | Meaning |
|---|---|---|
| `format` | required | `"openeik-drawing-read-set/0.1"` |
| `drawing`, `sequence` | required | the revision's |
| `sheet` | required | the pairs that the sheet as a whole read |
| `viewports` | required | one entry per viewport of the derivation, in its order: `{id, view, volume, read, inVolume}` |

A **pair** is `[path, hash]`, as Part VI, 8.3 says: a record's leaf path (`architecture/level-0/window/study-window-east`) and its hash; a kept file's own path (`drawings/profile.json`) and the hash of its text as a canonical JSON string. A view gives one pair for `view.json` (`drawings/views/elevation/view-east/view`) and one for each detail chunk (`…/details/0000`): this part's digests include detail chunks, which Part VI, 8.3 leaves out (at 0.2, Part VI, 8.3 changes). Each list is sorted by path, each path once. Pairs are keyed by path, not by id, since ids are unique only within their list (Part I, 6, rule 1). A pair's path names a record by its list and id, and a view's chunk by its view's id and its index, in either layout of Part III, 4.4: a checker compares a pair with the record it names (Part III, 4.4, rule 9), so that a drawing issued before a building's change of layout reads the records that the change moved, its pairs' paths as they were.

In each viewport, `read` holds the pairs of what it consulted; `inVolume`, the pairs of the elements that the volume test (9.3) held at the source version and that `read` does not hold: elements in the view's volume, but hidden or left out; and `volume`, the test's parameters:

- a plan: `{kind: "plan", crop: [x0, x1, y0, y1], rotation, z: [bottom, top]}`, in plan coordinates and absolute elevations, `null` for an open end;
- a section or an elevation: `{kind, origin, direction, crop: [u0, u1, v0, v1], depth: [0, far]}`;
- a drafting view: `{kind: "drafting"}`, which holds no element;
- a 3D view: `{kind: "3d"}`, whose volume is the whole model (9.3): every element of the model is in its `read` or its `inVolume`.

Each volume also gives `margin`, in mm, added all round (the prototype's is 1).

A-101 at P02, the east facade, abridged (the records' hashes are the prototype's; the view's pairs differ from the prototype's single hash for it, so they are placeholders):

```json
{
  "drawing": "A-101",
  "format": "openeik-drawing-read-set/0.1",
  "sequence": 2,
  "sheet": [
    ["drawings/profile.json", "<hash>"],
    ["drawings/sheets/lhouse-sheet", "77c550a96736eb1e9d9ec4d520298443f1c8ba53072428d3a3ebb4290510a615"],
    ["drawings/sheets/outdoor-connection-sheet", "d3f3ed248c259e9a6c953927c06b421e9529ba116981f47bfe2aba6d84b0f8e2"],
    ["drawings/sheets/outdoor-sheet", "9456167e8d031e3c8278a8d6f06e788a73bb6c0a4fa8895146563c9673dffbcf"],
    ["drawings/sheets/roof-study-sheet", "615ca99ca792f6a421651c275e43fd16abf330dc4cd5b86235644db92196f30d"],
    ["project/levels", "59150d955ca1063efc9ded9d21922b75fcaa798727caf7ec38c1593c10b9f997"],
    ["project/project", "9ab0c9e2652ed85c7a57c778fe25032b4aae8653842814de01af0d620577c7d7"],
    ["project/title-blocks/generic-A1", "8777c6ddb90a00327b75fec4bfcbcd1933b5de0e5eb2d239f5ca2469f82cfe43"]
  ],
  "viewports": [
    {
      "id": "study-viewport-4",
      "inVolume": [["<path>", "<hash>"]],
      "read": [
        ["architecture/level-0/wall/study-wall-1", "66ddafe61eb58d12c58382c76e9e24824d84931556c6d160fb8a4ee789ec2f37"],
        ["architecture/level-0/window/study-window-east", "21df22d8acaa1d8d85a9be9238cf35e85a152cd808170c3b73b767dc5091b4eb"],
        ["drawings/views/elevation/view-east/view", "<hash>"],
        ["types/architecture/window/type-window", "4f7a344fcf17aea3d91367c66256bef49a51675ceeb6c6cf75d7e2a6b8ad9b30"]
      ],
      "view": "view-east",
      "volume": {"crop": [-4200, 13500, -700, 6500], "depth": [0, 30000], "direction": [-1, 0], "kind": "elevation", "margin": 1, "origin": [14000, 0]}
    }
  ]
}
```

The prototype's viewport held 123 pairs in `read` and 23 in `inVolume`. At P01 the window's hash was `2ea87a7a92fabbdbd598ac1235a0572ac30a900e42dd1811aa1b284ce14c86fd`, its hash in `examples/l-house`.

### 9.3 The view volume and element boxes

The volume is where a view can show something:

- a plan: its crop, in plan, and in height everything that starts below its cut plane (the level's elevation plus `cutHeight`, or the view range's `cut`), up to the top of its view range where it draws above the cut;
- a section or an elevation: its crop across (u along the right normal of `direction`, v up), and from its origin's plane to `far` behind it;
- a dependent view: its primary's range, as the reference implementation's `src/core/views/viewInheritance.ts` gives it;
- a 3D view: the whole model, so that its viewport is stale when any element changes, enters or vanishes: a false alarm at times, never a miss.

A checker gives each element a **box** that holds every line any conforming generator draws for it: the bodies that a core compiler derives (Part IV), grown 150 mm across and 100 mm up and down for the joins that it does not derive; a door or a window, from its host wall's line, `along`, `width`, `offset` and `height`, across the wall's thickness plus 100 mm and a door's swing; a slab edge or a gutter, from its host; a railing on a stair, whose line follows the stair's flights and whose profile gives no heights (Part II, 12.2, rule 4), across its profile and the stair's, and up from the stair's base to its top (Part IV, 5.1) and the railing's height over it; a beam of a framing family, from every point of its parts as the family's expressions place them over its set-out line (Part II, 5.4), swept across by their widths; and any other element from its profile and its sizes, grown 50 mm. An element of a category that the checker cannot box is taken to be in every volume of its level's views: a false alarm, never a miss. So is a room, whose drawing (its name, area and fill) reaches across its whole boundary; and so is a placed copy (Part II, 10): the elements of a copy are derived, `<copy id>/<element id>`, and the copy's record is in every volume of the views of its original's level. A view's direction is a direction only: a checker uses it as a unit vector, whatever its length.

*Note (informative).* In the prototype, every one of the L-house's 130 boxes held the reference implementation's own bounds of the element. The prototype does not cover view ranges, scope boxes, rotated plans, drawing above the cut, or 3D views, whose volume is the whole model: the library extends it, and the freshness vectors test each.

### 9.4 Freshness

A revision is checked against a version of the line of work it tracks (`source.ref`), or against any version a caller names, such as a proposal's head. In order:

1. **Same tree.** Where the version's root tree hash equals `source.root`, and the generator is the same, the revision is `current`.
2. **Sheet.** Where a pair of `sheet` has a path that holds a different hash now, or none, the whole drawing is stale. So is it where `sheet` holds the pair of a sheet record other than the drawing's own (`derivation.sheet.id`), as a sheet that lists or counts the project's sheets does (9.1), and the version holds a sheet record whose path `sheet` does not hold: that sheet entered, since such a list would now name it. This too is a dependency on absence, as in step 4. And where `sheet` holds a view's pair, as a register's does, a detail chunk of that view that `sheet` does not hold is new since the issue: changed, as in step 3.
3. **Read.** In each viewport, where a pair of `read` or `inVolume` names a record that changed or vanished, or the viewport's view has a detail chunk that `read` does not list, that viewport is stale. An element that was in the volume, undrawn, counts only when it changed.
4. **Entered.** In each viewport, an element whose box now meets the volume, and whose path is in neither `read` nor `inVolume`, entered it, and the viewport is stale: this is a dependency on absence.
5. **Checked.** Where steps 2 to 4 found something, a candidate drawn with the recorded generator, whose every element group and annotation group has the issued `data-eik-hash` and bytes, makes the revision `checked` at that version: current, with no new revision. An element's groups are compared by viewport, element and part: where an element has parts (6.7), the candidate holds each of its issued parts, with the same bytes, and no other. Where the derivation's `generator.order` is `paint`, each layer's groups also come in the same order: in paint order, a group that moves behind another is drawn differently with the same bytes. Without a candidate, it stays `stale`.
6. **Generator.** A checker that knows the generator that would draw now (a candidate's derivation, or what the caller names), and finds that it differs from `derivation.generator` or `fonts`, reports `wouldDrawDifferently`: a state of its own, never a change of the model, and never clouded as a design change without a person's decision. Beside a `stale` verdict it is reported apart.

In steps 2 to 4 a path is compared by the record it names (9.2): a record that only moved, as the change of layout of Part III, 4.4 moves records, its hash the same, has not changed, and is held where `read` or `inVolume` holds its path before the move. A revision issued before the change of layout is therefore `current` after it, until a record it read changes.

The verdicts are `current`, `checked`, `stale` (with its causes: for the sheet and for each viewport, the paths that changed, vanished and entered), `wouldDrawDifferently`, and `unlinked` (no read set: not worked out). Freshness is derived, and never stored (section 16).

### 9.5 Which viewport, and which elements (informative)

A cause is matched to the sheet, or to the viewport whose list holds its path. Within a viewport, the elements whose drawing may change are the changed and entered elements it holds (direct), and the elements of its `read` one reference away from them (Part II, 7, and 3.1 here): a wall hosting a moved window. In the prototype this predicted every element whose drawing changed, and over-predicted the wall in two facades where its drawing did not change.

### 9.6 The prototype's evidence (informative)

On the L-house (130 elements, 13 views, 4 sheets), after `study-window-east` moved 500 mm, hashes alone flagged A-101 and A-104 as stale in about 20 ms warm (84 ms cold, 68 of them reading and hashing the model), and missed nothing; drawing them again took 0.47 s in the reference implementation's engine; and A-102 and A-103, which do not show the window, came out byte-identical. Of A-101's six viewports, three (the south, north and west facades) were false alarms, which the engine's own culling or a candidate clears. A column created on a scratch copy flagged nine viewports, eight of which drew it: no miss, and one false alarm. Each sheet drew the same bytes twice.

### 9.7 Rules

1. The read set MUST validate against its schema; its `drawing` MUST be its revision's, and its `sequence` the revision's, or, for a change of status alone (8.2), that of the revision whose drawing it keeps; each list MUST be sorted by path, each path once.
2. `viewports` MUST hold exactly the derivation's viewports, by id and view.
3. A viewport's `read` MUST hold the pair of every element drawn in it (each id of its `data-eik-id`), of its view and of each of its view's detail chunks.
4. A viewport's `read` MUST hold the type of each element drawn, and the sheet's list MUST hold the sheet record, its title block, the project, the levels and the drawing profile. The rest of 9.1 is the issuer's to meet.
5. `inVolume` MUST hold, with their hashes, the elements that the volume test held at the source version and that `read` does not.
6. A revision MUST be current against its own source version: one that its own source version makes stale has an incomplete read set.
7. A checker's boxes MUST hold every line any conforming generator draws for the element (9.3).
8. A checker MUST NOT report a revision `current` or `checked` when step 2, 3 or 4 of 9.4 finds a cause, and MUST name each cause.
9. A checker MUST report a change of generator alone as `wouldDrawDifferently`, never as `stale`.
10. A checker MAY report `checked` only from a candidate drawn with the recorded generator (9.4, step 5).

## 10. Change items and revision clouds

### 10.1 Change items

`changes`, in the revision record, holds change items: `{id, what, by, elements?, viewports?}`. `id` is unique in the revision (`chg-1`). `what`, 1 to 400 characters, is the description that the revision table and the change memo show, and that a host shows first under what changed: it names elements as people know them, by their names, never by their ids ("East window moved 500 mm", not "study-window-east moved 500 mm"). `by` says who wrote it: `person`; `agent`, drafted from the field-level diff of the records, which the person issuing accepts by issuing; or `derived`, from a template. `elements` names the elements whose drawing it describes, and `viewports` where they are (or `sheet`). The items are recorded whether or not clouds are drawn, so that a viewer can still show what changed.

### 10.2 Clouds, per update

The profile sets a default for each status (`statuses[].clouds`); the person issuing switches clouds on or off for any update (`clouds` `on` or `off`); and where the changes cover most of the sheet, the tool proposes `throughout`, a note "revised throughout" in the revision row instead of a sheet of clouds (the steward's refinement of 15 October 2026). Clouds follow the visible change, not elements' boxes: in the prototype, boxes round the changed elements covered 4 821 mm² of the east facade where the visible change fitted in 672 mm², and in the section the change was invisible and needed no cloud. At `asBuilt`, the profile removes them. A cloud is a closed path in the `revisions` layer, in paper mm, with `data-eik-rev`, `data-eik-change` and `data-eik-viewport`, beside its revision mark, a triangle with the code. Where the profile's `keepEarlierClouds` is false, only this revision's clouds remain.

### 10.3 Rules

1. Every `data-eik-change` of the `revisions` layer MUST name a change item of the revision, and every `data-eik-viewport` there a viewport of the sheet or `sheet`.
2. Change item ids MUST be unique in the revision.
3. With `clouds` `off` or `throughout`, the `revisions` layer MUST hold no cloud of this revision.
4. With `keepEarlierClouds` false, the `revisions` layer SHOULD hold only this revision's clouds.
5. Clouds SHOULD enclose the visible change, and none SHOULD mark a change that is not visible.
6. An element whose `data-eik-hash` differs between the superseded revision's SVG and this one's (any of its parts', where it has parts), or that is in one only, SHOULD be in a change item's `elements`.

## 11. The register

### 11.1 The file

`drawings/register/<drawing>.json` is append-only: every snapshot ever taken of one drawing. A host lists the registers of a line of work as its drawing register.

| Member | | Meaning |
|---|---|---|
| `format` | required | `"openeik-drawing-register/0.1"` |
| `drawing` | required | the number |
| `kind` | required | `linked` or `unlinked` |
| `entries` | required | the events, oldest first |

An entry is one of:

- `{event: "issued", sequence, revision, date, state, sharedFor?, status, record, svg?, derivation?, asset?, source?, by?}`: a revision. `record` is its revision record's hash; `svg` its SVG's digest and `derivation` its derivation's hash, for a linked drawing, or `asset` its file's digest, for an unlinked one; `source`, `{version, root}`, what it was drawn from, so that the history finds its files; and `by`, who issued it, as its revision record's `by` (8.5, rule 12), so that a sheet's revision table can sign each of its rows from the register;
- `{event: "rendition", sequence, asset, standard, signed}`: a PDF rendition kept as an asset (section 15);
- `{event: "withdrawn", date, by, reason?}`: the drawing's end. Its last revision stays in the folder.

Superseded is implied: each `issued` entry supersedes the one before. The issuer adds the entries, in the model; a host never writes the register. Two lines of work that issue the same drawing meet here when they are merged, as a conflict of the register, which a person settles by issuing again from the merged line.

### 11.2 Example

A-101 after P02:

```json
{
  "drawing": "A-101",
  "entries": [
    {
      "date": "2026-10-15",
      "derivation": "<hash>",
      "event": "issued",
      "record": "<hash>",
      "revision": "P01",
      "sequence": 1,
      "sharedFor": "review",
      "source": {"root": "44562e74bd618479c947bce600227e538cf3bed05cf573bf76947a27884e12b6", "version": null},
      "state": "shared",
      "status": {"code": "S3", "profile": "example"},
      "svg": "742dadf029a3a787dd9589bc1ba00f3d1e0fc157b0584246e81d08e1e7db2efe"
    },
    {
      "date": "2026-10-15",
      "derivation": "<hash>",
      "event": "issued",
      "record": "<hash>",
      "revision": "P02",
      "sequence": 2,
      "sharedFor": "review",
      "source": {"root": "6b572fb1e20adea6f4d012a31588f32aef0dc7995796d2630497fb3f74e1f378", "version": null},
      "state": "shared",
      "status": {"code": "S3", "profile": "example"},
      "svg": "7e7e0bbe28f97181127a5e2b7f624004ae6848f9ae322eb81d68c82c018fa05c"
    }
  ],
  "format": "openeik-drawing-register/0.1",
  "kind": "linked"
}
```

### 11.3 Rules

1. The register MUST validate against its schema.
2. Entries MUST be in order: each `issued` entry's `sequence` is the one before's plus 1, from 1; a `rendition` names a sequence already issued; nothing follows `withdrawn`.
3. The last `issued` entry MUST be the drawing's folder: its `sequence`, `record` and `svg` (or `asset`) MUST be the current revision's, and its `by`, where it gives one, the current revision record's `by`.
4. A later version's register MUST hold every entry of an earlier version's, unchanged and in order.
5. `kind` MUST be the kind of every revision of the drawing; a `linked` entry MUST give `svg`, `derivation` and `source`, an `unlinked` one `asset`.
6. An unlinked drawing MAY be registered, reviewed and issued as any other (8.3).

## 12. Issues

### 12.1 The file

`drawings/issues/<issue>.json` is a transmittal: the release of revisions to recipients for a purpose (D9).

| Member | | Type | Meaning |
|---|---|---|---|
| `format` | required | `"openeik-drawing-issue/0.1"` | |
| `id` | required | Part I, 6, minted without coordination | the file's name: a random id such as a ULID, or one derived from the record's content, which a deterministic issuer needs |
| `purpose` | optional | `{profile, code}` | a purpose of the profile |
| `sent` | required | `{by, at}` | who sent it, a person of the registry, and when; where only the day is known, `at` is midnight UTC of that day |
| `revisions` | required unless `versions` is given | list, 1 to 10 000 | `{drawing, sequence, revision, record}`: the revisions released, each by its revision record's hash |
| `versions` | optional | list, 1 to 20 | the versions of the model released, each by its root tree hash (Part XVIII, 3.1) |
| `renditions` | optional | list, at most 10 000 | the prints sent with it, each by its digest, and kept nowhere (Part XVIII, 3.1) |
| `terms` | optional | list, at most 20 | the terms it is sent under, by their digest (Part XVIII, 3.1) |
| `recipients` | required | list | `{person}`, a person of the registry, or `{name, organisation?}` for someone outside it; never an email address |
| `note` | optional | string, at most 2000 characters | |

An issue releases revisions, versions of the model or both. `versions`, `renditions` and `terms` are Part XVIII's (its section 3.1), which says what they mean and how they are checked.

Delivery, links and read receipts are the host's, and stay there. The authoring tool writes this file once, when it issues, and it is not changed after. A host that then sends the issue, once or several times, and to recipients the file may not name, keeps its sendings, and offers each recipient's as a receipt (Part XVIII, 5), which the authoring tool saves beside the issue, as it saves reviews (section 13) and sign-offs (14.4). A host never writes the model.

```json
{
  "format": "openeik-drawing-issue/0.1",
  "id": "iss-01jc4p7v2m",
  "purpose": {"code": "S3", "profile": "example"},
  "recipients": [
    {"person": "person-k2m9xa"},
    {"name": "Site manager", "organisation": "Main contractor"}
  ],
  "revisions": [
    {"drawing": "A-101", "record": "<hash>", "revision": "P02", "sequence": 2},
    {"drawing": "A-104", "record": "<hash>", "revision": "P02", "sequence": 2}
  ],
  "sent": {"at": "2026-10-15T14:00:00Z", "by": "person-k2m9xa"}
}
```

### 12.2 Rules

1. The file MUST validate against its schema, and its `id` MUST be its name read back.
2. Each revision it lists MUST be in its drawing's register, with that `sequence` and `record`.
3. `purpose`, where given, MUST be a code of the profile's `purposes`.
4. It MUST list each drawing once.
5. Recipients and `sent.by` MUST follow 5.2, rules 2 and 3.
6. An issuer SHOULD write one issue record each time it issues one or more revisions together: the revisions, the purpose, `sent.by` and `sent.at`, and the recipients where it knows them. A host that lists what was released together reads these records.

## 13. Reviews

A review is a round on one revision, as structured data. A host keeps reviews and their working state; at the close it offers the review in this file, and the authoring tool saves it into the model with the next revision it issues, so that the folder holds the conclusion.

### 13.1 The file

`drawings/reviews/<review>.json`:

| Member | | Type | Meaning |
|---|---|---|---|
| `format` | required | `"openeik-drawing-review/0.1"` | |
| `id` | required | Part I, 6 | unique without coordination across hosts and forks, such as a ULID, and never a host's sequence number; the file's name |
| `drawing`, `sequence`, `revision` | required | | the revision reviewed |
| `svg` (linked) or `asset` (unlinked), and `record` | required | digests | the SHA-256 of `drawing.svg`, or of the unlinked drawing's asset, and the hash of `revision.json`, as reviewed |
| `compared` | optional | `{sequence, revision, record}` | the revision the round compared it with |
| `profile` | required | `{id, code?, purpose?}` | the drawing profile, and the status or purpose code the round reviewed for |
| `opened` | required | `{by, at}` | |
| `due` | optional | date | |
| `closed` | optional | `{by, at}` | absent while the round is open |
| `reviewers` | required | list | `{person, role, decision?}`: `role` is a plain value, and `decision`, `{code, at}`, a code of the profile's `decisions` (decision A or B) |
| `items` | required | list (13.2) | |

### 13.2 Items, anchors and states

An item:

| Member | | Meaning |
|---|---|---|
| `id` | required | unique in the review |
| `kind` | required | `comment`, `cloud`, `arrow`, `text`, `measure` or `highlight` |
| `author` | required | `{person}`, or `{agent: {name, version}, actingFor, acceptedBy?, acceptedAt?}`: an agent's item counts for nothing until a person accepts it |
| `at` | required | when it was made |
| `text` | optional | at most 4000 characters |
| `replies` | optional | `[{author, at, text}]` |
| `state` | required | a code of the profile's `reviewStates` |
| `history` | optional | `[{state, by, at}]`, oldest first |
| `anchors` | required, and may be empty | tried in order: `{element, feature?, viewport, position?}`, an element the revision draws (`data-eik-id`), a named feature, the viewport, and a position `[s, t]` in the feature's frame |
| `sheet` | required | `{x, y, page?}`: the fallback, in mm of paper from the sheet's top-left corner; `page`, counted from 1, for an unlinked drawing's PDF of several pages |
| `shape` | optional | `{points, closed?, form?}`, in paper mm: the geometry of a cloud, an arrow, a highlight or a measure, or the frame that holds a text's or a comment's words, as a text box has one. A measure's value is derived from its points and the viewport's scale. `form` says how the points are drawn (below) |
| `anchorState` | required when the round is closed | `attached`, `moved`, `elementOnly` or `orphaned`, as at the close |
| `change` | optional | the change item of the revision that the item is about |
| `carriedFrom` | optional | `{review, item}`: the item of an earlier round that it carries forward |
| `bcf` | optional | the GUID of its BCF topic, for round trips |
| `attachments` | optional | `[{asset, mediaType}]`: files kept as assets by digest, photographs for example. They are personal data, kept in every version that holds them (Security and privacy, 2.4) |

**Shapes.** Added with open question 61, from plan 688: an item's kind says what it means, and its shape's `form` how it is drawn, so that a markup keeps its form from one application to another.

- `freehand`: a stroke of a pen, drawn straight between points taken close together;
- `polyline`: straight between its points, open;
- `polygon`: straight between its points, closed;
- `rectangle`: four points, the corners of a rectangle in order, which may be turned;
- `ellipse`: the ellipse that fills the rectangle of its four points, its axes along the rectangle's sides.

Without `form`, the points are drawn straight, closed where `closed` is true, as before. A reader that does not know `form` draws them so: a rectangle then draws as itself, and an ellipse as the rectangle that holds it. Colour, line weight and the other matters of style are no member of an item: an application keeps its own as extension members under its prefix (Part I, 11.2), `stomra:color` for one, which every reader keeps.

*Note (informative).* The reference implementation's markups fit so. A cloud and a cloud with a note are a `cloud`, with its text; a callout and an arrow are an `arrow`, an arrow with text being a callout; a text, a text box and a note are a `text` or a `comment`, a text box's box its shape; a pen stroke, a polyline, a polygon and a highlight are a `highlight` of the form `freehand`, `polyline` or `polygon`; a rectangle and an ellipse are a `highlight` of their form, the reference's two opposite corners written as the four of the rectangle they span; and a measure is a `measure`. A callout's box and a cloud's note box have no member here, nor a pen's strokes of one word: an extension member keeps them, until a second application asks for them. Reference: `markupKinds`, `geometryProblem` and `rectPoints` in Stomra's `src/core/review/markups.ts` (Stomra work 271).

Seen ticks, read receipts and each person's progress are the host's, and stay out of the file. The six review states of BEAst Effektivare granskning 2.0 (new, rejected, to act on, acted on, needs new review, elsewhere) are the Swedish default of an authoring tool's profile, not a list of the standard.

### 13.3 Example

A round on A-101 P02, compared with P01, with one comment on the moved window:

```json
{
  "compared": {"record": "<hash>", "revision": "P01", "sequence": 1},
  "drawing": "A-101",
  "format": "openeik-drawing-review/0.1",
  "id": "rv-01jc4n2x8q",
  "items": [
    {
      "anchors": [{"element": "study-window-east", "viewport": "study-viewport-4"}],
      "at": "2026-10-15T15:02:00Z",
      "author": {"person": "person-r4t8wd"},
      "change": "chg-1",
      "id": "i-1",
      "kind": "comment",
      "sheet": {"x": 392.67, "y": 334.67},
      "state": "new",
      "text": "Check the sill height after the move."
    }
  ],
  "opened": {"at": "2026-10-15T14:05:00Z", "by": "person-k2m9xa"},
  "profile": {"code": "S3", "id": "example"},
  "record": "<hash of revision.json>",
  "reviewers": [{"person": "person-r4t8wd", "role": "checker"}],
  "revision": "P02",
  "sequence": 2,
  "svg": "7e7e0bbe28f97181127a5e2b7f624004ae6848f9ae322eb81d68c82c018fa05c"
}
```

The sheet position is the window's place on the east facade at P02, from the prototype's boxes.

### 13.4 Rules

1. The file MUST validate against its schema, and its `id` MUST be its name read back.
2. `drawing`, `sequence`, `svg` or `asset`, and `record` MUST name a revision in the drawing's register, by its digests.
3. `profile.id` MUST be the profile's; every item's `state` and every state of its `history` MUST be a code of its `reviewStates`; every `decision.code` MUST be a code of its `decisions`.
4. An item's anchors SHOULD name elements the reviewed revision draws, and viewports of its sheet.
5. A closed review MUST be frozen: no item, reply, state or decision is later than `closed.at`, and a later version holds the file unchanged.
6. `carriedFrom` SHOULD name an item of another review of the same drawing, where the folder holds it.
7. `change` SHOULD name a change item of the reviewed revision.
8. The people it names MUST follow 5.2, rules 2 and 3.
9. A shape's `form` `polyline` MUST NOT be `closed`, and `polygon`, `rectangle` and `ellipse` MUST be; a `polygon` MUST have three points at least, and a `rectangle` or an `ellipse` exactly four, the corners of a rectangle in order: the midpoints of its two diagonals within 0.01 mm of each other, and their lengths within 0.01 mm.

## 14. Sign-offs

"A named person approved this exact drawing with this status" is a statement about digests, signed apart from the files, so that signatures never change a drawing's bytes, and several people can sign in turn (D4). Signing is optional, and scales with the stage: the profile says which statuses need which sign-offs (`requires`). A sketch shared for information needs none, and still gets its derivation and read set, at no cost to anyone.

### 14.1 The statement

A sign-off's statement is an in-toto Statement v1:

- `_type`: `https://in-toto.io/Statement/v1`;
- `subject`: exactly two, each `{name, digest: {sha256}}`: `drawings/issued/<drawing>/drawing.svg`, with the SHA-256 of its bytes, and `drawings/issued/<drawing>/revision.json`, with its hash (Part III, 6); for an unlinked drawing, its asset in place of the SVG;
- `predicateType`: `https://openeik.org/attestation/drawing-signoff/v0.1`;
- `predicate`:

| Member | | Meaning |
|---|---|---|
| `drawing`, `sequence`, `revision` | required | the revision signed |
| `status` | required | `{profile, code, purpose?}`: the revision's |
| `action` | required | `checked`, `reviewed`, `approved`, `authorised`, `accepted`, `rejected`, `withdrawn` or `checkedByTool` |
| `role` | required | the role in which the signer acts, a plain value (`approver`) |
| `signer` | required | a person of the registry |
| `model` | required | `{version, root}`: the derivation's source |
| `at` | required | the signer's own time; the time-stamp's is the trusted one |
| `decision` | optional | a code of the profile's `decisions`, where the action is a decision (`approved`, `rejected`, `authorised`, `accepted`) |
| `review` | optional | `{id, hash}`: the closed review round that the sign-off concludes |
| `withdraws` | required for `withdrawn` | the hash of the sign-off file it withdraws |
| `evidence` | optional | for `checkedByTool`: `{path, hash}` of a result manifest (Part VI, 8) that holds what the tool found |

```json
{
  "_type": "https://in-toto.io/Statement/v1",
  "predicate": {
    "action": "approved",
    "at": "2026-11-12T09:14:00Z",
    "decision": "approved",
    "drawing": "A-101",
    "model": {"root": "6b572fb1e20adea6f4d012a31588f32aef0dc7995796d2630497fb3f74e1f378", "version": null},
    "revision": "P02",
    "role": "approver",
    "sequence": 2,
    "signer": "person-k2m9xa",
    "status": {"code": "S3", "profile": "example"}
  },
  "predicateType": "https://openeik.org/attestation/drawing-signoff/v0.1",
  "subject": [
    {"digest": {"sha256": "7e7e0bbe28f97181127a5e2b7f624004ae6848f9ae322eb81d68c82c018fa05c"}, "name": "drawings/issued/A-101/drawing.svg"},
    {"digest": {"sha256": "<hash of revision.json>"}, "name": "drawings/issued/A-101/revision.json"}
  ]
}
```

### 14.2 The file

`drawings/issued/<drawing>/signoffs/<n>.json` holds one signer's action:

| Member | | Meaning |
|---|---|---|
| `format` | required | `"openeik-drawing-signoff/0.1"` |
| `envelope` | required | a DSSE envelope: `payloadType` `application/vnd.in-toto+json`; `payload`, the statement's JSON in base64; and `signatures`, `[{keyid, sig, webauthn?}]`, over DSSE's pre-authentication encoding. `webauthn`, `{authenticatorData, clientDataJSON}` in base64url, is given where a passkey signed (14.7, rule 9) |
| `timestamps` | required | `[{keyid, token}]`: for each signature, an RFC 3161 time-stamp token (the base64 of its DER) whose message imprint is the SHA-256 of the signature's bytes |
| `legal` | optional | `{asset, sha256}`: the container of the legal tier (14.6) |

`<n>` SHOULD be `s-` followed by the first 12 hexadecimal digits of the envelope's hash, as run ids are made (Part VI, 8.5): two people signing on two lines of work never take one name.

### 14.3 Verifying offline

A verifier checks the SVG profile and the canonical form (section 6); the records (sections 8 and 9); that every `data-eik-id` resolves at the recorded version; and, optionally, that drawing the sheet again gives the same bytes. Then, for each sign-off, it checks the DSSE signature with the registry's key, the time-stamp and its imprint, the key's window of validity at the time-stamp's time, the subjects' digests against the files, and the predicate type, explicitly. A verifier needs the folder and the time-stamp authority's certificate, and no host.

### 14.4 Where sign-offs come from

An authoring tool writes sign-offs, or saves the ones a host made: a host keeps sign-offs and verifies them, and never writes the model. The approver signs the digests of the exact files that will be issued, shown as they are, so that what was reviewed is what is issued. A sign-off whose subjects are an earlier revision's signs that revision: it is saved in the same `signoffs/`, and checked against that revision's entry in the register.

### 14.5 Agents and tools

An agent signs with its own registered key (a record of the registry of the kind `agent`), and only `checkedByTool`: it never signs an approval or any other decision (D7). This keeps the principle that only a named person approves, which is not that only a person signs. What the tool found belongs in a result set (Part VI, 8), which the statement names as `evidence`; the statement says only that the tool checked these exact digests.

### 14.6 The legal tier

Where the client or the law asks, the same statement and files are signed as an ASiC-E container with XAdES (B-LTA when archived), with a qualified or advanced signature made through a federated signing service, and the PDF rendition with PAdES if the recipient asks (D4). The container is an asset (`legal`). Validating it needs an ETSI validation report (TS 119 102-2), outside the library. Its details wait for a legal review.

### 14.7 Rules

1. The file MUST validate against its schema; its envelope's `payloadType` MUST be `application/vnd.in-toto+json`, and its payload MUST decode to an in-toto Statement v1.
2. `predicateType` MUST be `https://openeik.org/attestation/drawing-signoff/v0.1`, and the predicate MUST validate against its schema.
3. The subjects MUST be exactly two, by path the drawing's `drawing.svg` (or the unlinked drawing's asset) and `revision.json`, with the digests of a revision in the drawing's register: the `svg` (or `asset`) and `record` of the `issued` entry whose `sequence` the predicate names.
4. The predicate's `drawing`, `sequence`, `revision` and `status` MUST be those of the revision it signs: the register's `issued` entry of that `sequence`.
5. `signer` MUST name a person of the registry, and each signature's `keyid` a key of that record.
6. A signer of kind `agent` MUST sign only `checkedByTool`, and a person MUST NOT sign `checkedByTool`.
7. `decision`, where given, MUST be a code of the profile's `decisions`; it SHOULD be given for `approved`, `rejected`, `authorised` and `accepted`.
8. Every signature MUST have a time-stamp. The schema states this rule.
9. Each signature MUST verify with its key; its time-stamp MUST verify, and its imprint MUST be the signature's SHA-256; and the key MUST have been valid at the time-stamp's time. With `webauthn` (a passkey): clientDataJSON's `type` MUST be `webauthn.get` and its `challenge` MUST be base64url of the SHA-256 of DSSE's pre-authentication encoding; authenticatorData's rpIdHash MUST be the SHA-256 of the key's `rpId`, and its user-present flag MUST be set (user-verified SHOULD be set for decisions); `sig` MUST verify over authenticatorData ‖ SHA-256(clientDataJSON). The time-stamp's imprint stays the SHA-256 of `sig`.
10. A `withdrawn` statement's `withdraws` MUST name a sign-off of the same revision, by the same signer.
11. `review`, where given, MUST name a closed review of the same revision by its hash.
12. The sign-offs that the profile's `requires` asks for the revision's status SHOULD be present and not withdrawn.
13. The file's name SHOULD be made as 14.2 says.

## 15. The PDF rendition

### 15.1 What it is

At issue, a PDF/A-3b is made from the issued SVG (D6): at exact scale, its page the sheet's paper, its hatches as vectors, tagged, and byte-reproducible, with a fixed date (the revision's) and an identifier derived from the content. The SVG is attached with the relationship Source and the media type `image/svg+xml`; `revision.json` and `read-set.json` with Data; and the sign-off files that exist with Supplement, as e-invoices carry their XML. Attach first, then sign: a PAdES signature then covers the attachments. A recipient without OpenEik opens a normal PDF; one with OpenEik takes the source-mapped SVG out of it and checks it. A rendition is reproducible, so it is kept only when it is signed, as an asset that a `rendition` entry of the register names; otherwise a host makes it on demand.

### 15.2 Rules

1. A rendition kept in the folder MUST be an asset `assets/<sha256>.pdf`, named by a `rendition` entry of its drawing's register.
2. It MUST be PDF/A-3b; its page MUST be the sheet's paper; it MUST carry the attachments of 15.1 with their relationships and media types.
3. Each attachment MUST be byte for byte the issued file.
4. The same issued files, renderer version and fonts SHOULD give the same bytes.
5. A writer SHOULD NOT keep an unsigned rendition: it is derived (section 16).

### 15.3 The margin note

By the steward's decision of 25 October 2026 (plan 688, decision 6), a PDF says on its paper what it is a copy of. A rendition is a print: it is never the record, and two renditions are never compared to find what changed; the revisions they were printed from are (Part XVIII, 7.1). A rendition made at issue, and one printed again later on demand, carry the same note.

1. A rendition MUST carry a margin note below the drawing frame, in the bottom margin of its page, over nothing the issued drawing draws.
2. The note MUST name, in one or two lines, the drawing's number and revision code, the building (its project's name, or its id), and the version the revision was drawn from (the derivation's `source.root`, of which its first 12 hexadecimal digits are enough), and MUST say in a sentence that the print is a copy, as paper is. It MAY also name the revision record's hash.
3. The note MUST carry a QR code, at the bottom right corner of the page on a white square, that encodes a URL naming the revision by its SVG's SHA-256 (the revision record's `svg.sha256`), or an unlinked drawing by its asset's, at which a host answers whether that revision is current. The rest of the URL is the host's.
4. The note MUST hold no time of printing, and nothing that differs by the person who prints or reads it, so that rule 4 of 15.2 holds: every date the file holds (its creation and modification dates, its document information dictionary, its XMP metadata and its attachments' dates) MUST be the revision's `date`, and the file's identifier MUST be derived from the building, the version and the SVG's digest (or the asset's). A PDF of several revisions, such as an issue's sheets printed as one, carries on each page the note of that page's revision; every date it holds MUST be the latest of its revisions' dates, and its identifier MUST be derived from the building, the versions and every revision's SVG digest (or asset's). A PDF of one revision stays as this rule says.
5. The host or application that prints MAY name itself as the PDF's creator and producer, and in the check URL of rule 3, as an IFC print names who printed it (Part XVIII, 7.2, rule 4). The note's own words SHOULD NOT name it: they say what the print is a copy of, which is the same whoever prints it. So one revision printed by two hosts differs only in those names and that URL.

*Note (informative).* One host prints the note in 5 pt Noto Sans, on one or two lines, with a QR code 7 mm square, 2 mm from the paper's edges: a note such as "A-101 P02 · Example House · version 6b572fb1e20a · A print is a copy, like paper: scan the code to see whether it is current". Two of its prints of one revision came out byte-identical, and pass PDF/A-3b. The note's typography is the application's, as the title block's graphics are (1.2).

## 16. What is derived, and the stated exception

### 16.1 Derived, and never stored

These are derived, and never stored (Part IV, 1):

- the live drawing of a view or a sheet, and every preview, thumbnail and cache (`.eik/drawings/`, hidden);
- candidates: drawings drawn again from a later version or a proposal, to check freshness or to show a change;
- freshness verdicts and their causes (9.4), and the impact of a proposal;
- comparisons of two revisions, element by element, and descriptions made from them, until a person writes one into a change item;
- dimension values and tag texts, worked out when drawn: an issued SVG holds them as drawn;
- anchor states (3.3), worked out when drawn: an issued SVG records them;
- an unsigned PDF rendition, and an SVG made from an outside PDF for viewing;
- every other print: a PDF printed from a revision on demand, and an IFC exported from a version (Part XVIII, 7). A print is a copy, like paper: never the record, and never compared to find what changed;
- the register as a host lists it, from the register files.

### 16.2 The stated exception

Issued drawings are derived data that the folder keeps: a deliberate exception to "derived data is never stored" (Part IV, 1, rule 2), as a release keeps its built binaries (D2). The exception is narrow. An issued drawing is written only when someone takes a snapshot, it is never changed, and it is never read as model data: no record is derived from it, and no compiler reads it.

## 17. Commands and operations (reference behaviour)

### 17.1 Commands

Commands are Part V's:

- `sheet.upsert` and `sheet.delete` move from the application records of the reference vocabulary (Part V, 2.3) into the documentation profile (Part V, 2.2), on the sheet of section 2. `sheet.upsert` keeps its precondition: a new sheet counts as revision 0 (Part V, 3, rule 2). `view.delete` is still refused while a sheet uses the view (Part V, 5, rule 4). Deleting a sheet never touches its issued drawing: the issuer withdraws the drawing, with a `withdrawn` entry in its register.
- `person.upsert` writes a person whole (`expectedRevision` optional; a new person gets revision 0). No command deletes a person that a file of this part names, and a key is ended, not removed (5.2, rule 5).
- `detail.place`, `detail.update` and `detail.draw` carry `provenance` as a member of the item, and a processor keeps it. A processor that knows its caller is an agent (by the command envelope of Part XVII) writes `by` `agent` and `status` `proposed`, and refuses `status` `accepted` from an agent.
- Deletes are Part V, 5's: in the live model, an element's items go with it. Issued drawings are not records, and no command changes them.

### 17.2 The issue operation

Issuing is not a command: issued files are not records, as results are not (Part VI, 8). An issuer, at a version V of the line of work:

1. refuses while a view placed on the sheet holds a proposed agent's item (3.4, rule 4);
2. draws the sheet with its generator from V, and SHOULD draw it twice and compare the bytes, as the prototype did for every sheet;
3. compares each element's `data-eik-hash` (each part's, where it has parts) with the superseded revision's, and joins the result with the field-level diff of the records, to propose change items;
4. draws clouds as the update's choice says (10.2), and the revision row;
5. writes `drawing.svg`, `read-set.json` and `revision.json` together, replacing the superseded revision's files; the sign-offs stay;
6. appends the `issued` entry to the register;
7. writes one issue record for the revisions it issued together (12.2, rule 6);
8. records a version, so that the files are in the history: nothing issued is lost.

Sign-offs, reviews and issues are written as they come, by the authoring tool, which saves the files a host offers. In the reference implementation the issue operation is to be its "Issue", which writes canonical SVG and records into the folder instead of hashing and discarding the SVG.

### 17.3 The library

`@openeik/tools`, dependency-free like the rest of it, implements this part:

- `checkFolder(…, { proposals: ["drawings"] })` and `openeik check --proposal drawings`: every rule that the folder decides, and those that need the records concerned unchanged since the issue, where they are (19.2);
- `openeik drawing status <folder> [--generator <name>@<version>] [--candidate <dir>]`: the freshness of every issued drawing against the folder (9.4);
- `openeik drawing impact <folder> --against <folder | version>`: which issued drawings a later state or a proposal makes stale, with the causes and the elements likely to change (9.5);
- `openeik drawing diff <a.svg> <b.svg>`: element by element, by `data-eik-hash` (part by part, where an element has parts: changed where any part differs, added or removed where all its parts are), and the annotations by `data-eik-item`;
- `openeik drawing verify <folder> [<drawing>] [--history]`: 14.3, with DSSE and RFC 3161 verification; the legal tier is reported as not verified;
- `openeik drawing register <folder>`: the register of a folder, its current and superseded revisions;
- `openeik drawing bcf <folder> <review> [--out <file.bcf>]`: a review as BCF 3.0 (18).

A DSSE signature, Ed25519 or ECDSA P-256, verifies with the runtime's own cryptography. An RFC 3161 token needs a small DER reader, and the time-stamp authority's certificate as a trust anchor that the caller gives: both are in `verify`, not in `checkFolder`.

### 17.4 Agents

In Part VII: an agent host SHOULD offer an operation that answers, in one call, which issued drawings a version or a proposal makes stale, and why, and one that lists which issued drawings draw an element (a search of `data-eik-id`).

## 18. IFC and BCF (informative)

*This section is informative, as Part VIII is. It says how a bridge can export issued drawings to IFC, and reviews to BCF.*

- **Documents.** IFC has no sheet and no entity for an issued drawing. An issued revision exports as an `IfcDocumentInformation`: `Identification` the drawing number, `Name` the title, `Revision` the revision code, `ElectronicFormat` `image/svg+xml` (or `application/pdf` for an unlinked drawing), `Location` the file's path in the folder, `CreationTime` the date of issue, and `Status` mapped coarsely, `draft` and `shared` to FINALDRAFT and `published` and `asBuilt` to FINAL: IFC's `IfcDocumentStatusEnum` has only DRAFT, FINALDRAFT, FINAL and REVISION, besides NOTDEFINED.
- **What it shows.** An `IfcDocumentReference` to it, associated by `IfcRelAssociatesDocument` with each element the revision draws (its `data-eik-id`s).
- **Sign-offs.** Each sign-off as an `IfcApproval` (`Level` the status code, `Qualifier` the conditions of a B decision, `GivingApproval` the signer, `TimeOfApproval` the time-stamp's time), related to the document by `IfcResourceApprovalRelationship`. The OpenEik files stay richer than IFC: the export does not carry signatures.
- **Annotations.** Issued drawings are documents, not `IfcAnnotation`s. A bridge that exports views may write their detail items as `IfcAnnotation`s; 0.1 and this part specify none.
- **A model's reviews** (Part XVIII, 2) are on a version in 3D, and map onto BCF more directly than a drawing's (Part XVIII, 2.6).
- **Reviews** export as BCF 3.0 topics. BCF's viewpoints are 3D, so a topic carries the element's 3D viewpoint and a document reference to the revision by its digest, and the item keeps the topic's GUID (`bcf`) for round trips. A 2D extension of BCF is an open question, and not this part's. `@openeik/tools` exports a review so (`openeik drawing bcf`): a topic for each item, its GUID the item's `bcf` or one made from the review's id and the item's, and its status the drawing profile's review state; the reviewed drawing as a document of the BCF file, named by its digest, where the folder holds it; and for an anchored item a viewpoint that selects its elements by their ids as `AuthoringToolId` (BCF's `IfcGuid` being optional), with the orthogonal camera BCF 3.0 requires, framing them as the anchor's viewport looks.

## 19. Conformance

A claim of conformance to OpenEik 0.2 is a claim to what this section asks of each class it names, its own two among them ([Conformance](https://openeik.org/spec/0.2/conformance), 1 and 1.1). A claim to 0.1 says nothing about it.

### 19.1 What 0.1 asks covers these files in a model of 0.1

A reader keeps the files as 1.5 says. A validator does not refuse them, may warn about the unknown members of detail items (Part I, 10, rule 2), and checks sheets against the application record's schema only. A writer never removes or rewrites them (Part III, 7, rule 3).

### 19.2 What each class of 0.2 does

Of 0.2, each class does as follows:

- a **validator** checks the rules of this part that the folder decides, and those that need the records concerned unchanged since the issue (below) where they are. It reports a breach of a MUST as an error and a breach of a SHOULD as a warning (19.3), naming the file and the rule, as `XIV.6.12.3`;
- a **drawing validator**, a class of this part, is a validator that also works out freshness (9.4; 9.7, rules 7 to 10), verifies sign-offs (14.3), and checks the rules that need the history where it has the history;
- a **drawing issuer**, a class of this part, writes issued drawings: in the SVG profile (section 6), with the files, records and register of sections 7 to 11, a complete read set (9.1; 9.7, rule 6) and deterministic output (the same version and generator give the same bytes); it issues no drawing while a view placed on the sheet holds a proposed agent's item (3.4, rule 4), and never an SVG written by hand;
- a **reader** reads sheets and people as records, and the drawing files as this part defines them;
- a **writer** writes sheets and people at their paths, and keeps issued files as 7.4, rule 4 says;
- a **command processor** implements 17.1;
- a **version store** holds issued files and assets in its versions (7.3);
- a **bridge** may follow section 18, which is informative.

The folder alone decides most rules. These need more:

- **the records concerned unchanged since the issue**, their pairs in the read set equal to the model's: 3.4, rule 4; 6.12, rule 21, and the item that rule 23 names; 8.5, rule 2, for the sheet; and 9.7, rule 4, for the types. Where those records have changed, 3.4, rule 4 and 6.12, rule 21 need the history;
- **the history**, an earlier version of the folder: 5.2, rule 5; 7.4, rule 4; 9.7, rules 5 and 6, unless the folder is the revision's source version; 10.3, rule 6; 11.3, rule 4; 13.4, rule 5, for the file held unchanged, and rules 4 and 7 where the reviewed revision is not the current one;
- **cryptography beyond hashing**: 14.7, rule 9;
- **a PDF reader**: 15.2, rules 2 and 3; 15.3, rules 1 to 4;
- **the generator or a renderer**: 3.4, rule 6; 9.7, rules 7 and 10; 10.3, rule 5; and 15.2, rule 4. A validator does not check these; the conformance vectors show them.

### 19.3 The rules, by severity

A validator reports a breach of these rules as an error: 2.4, rules 1, 3, 6 and 7; 3.4, rules 2 to 5; 4.3, rules 1 to 3 and 5; 5.2, rules 1 to 5; 6.12, rules 1 to 3 and 5 to 25, and rule 27; 7.4, rules 2 to 6; 8.5, rules 1 to 7 and 11; 9.7, rules 1 to 6; 10.3, rules 1 to 3; 11.3, rules 1 to 5; 12.2, rules 1 to 5; 13.4, rules 1 to 3, 5, 8 and 9; 14.7, rules 1 to 11; 15.2, rules 1 to 3; and 15.3, rules 1 to 4.

It reports a breach of these as a warning: 2.4, rules 2, 4 and 5; 3.4, rule 1; 4.3, rule 4; 6.12, rules 4 and 26; 7.4, rule 1; 8.5, rules 8, 10 and 12; 10.3, rules 4 and 6; 13.4, rules 4, 6 and 7; 14.7, rules 12 and 13, rule 7 when a decision is missing, and rule 9 when a passkey's user-verified flag is not set for a decision; and 15.2, rule 5.

The other rules are permissions (8.5, rule 9; 11.3, rule 6); a requirement on issuers (12.2, rule 6); requirements on generators and checkers, which the conformance vectors show (3.4, rule 6; 9.7, rules 7 to 10); or rules on what is drawn and rendered, which a validator does not check (10.3, rule 5; 15.2, rule 4).

`@openeik/tools` checks this part for every model of 0.2, and for a model of 0.1 when asked: `checkFolder(…, { proposals: ["drawings"] })`, or `openeik check --proposal drawings`, and works out freshness and verifies sign-offs with `openeik drawing` (17.3). Its conformance cases are in the suite of 0.2 (`conformance/`). Besides the forms of 0.1's cases, a folder and the single-file form, they have two of their own: `freshness`, whose expected result gives each issued drawing's verdict and causes, and `verify`, which gives each sign-off's verification and the trust anchor it uses; and two classes of their own, `drawing-validator` and `drawing-issuer`.
