Skip to the content.

Формат проєкту Ramus

Створено: 2026-09-06 План переходу: FORMAT_MIGRATION_PLAN.md Вихідний стан до змін: START_REVIEW.md

Повний опис того, що лежить у проєкті. Призначений і для людини, і для агента, який правитиме ці файли, і для того, хто дописуватиме формат.


Зміст

  1. Що таке проєкт
  2. Синтаксис: діалект YAML
  3. Ідентифікатори
  4. Посилання на атрибути
  5. project.ramus
  6. attributes.yaml
  7. qualifiers/*.yaml
  8. Як записані значення
  9. Довідник типів атрибутів
  10. Модель IDEF0 у цих файлах
  11. Потоки: streams.yaml, properties/, attachments/
  12. .local/ — стан інтерфейсу
  13. Чого у проєкті немає
  14. Рецепти безпечного редагування
  15. Версія схеми й сумісність
  16. Старий формат .rsf
  17. Межі формату
  18. Що перевірено
  19. Як звірити цю доку з кодом

1. Що таке проєкт

Проєкт — каталог. Усередині текстові файли YAML і вкладення як є.

Модель.ramus/
├── project.ramus          опис проєкту: версія, плагіни, лічильники
├── attributes.yaml        усі атрибути моделі
├── qualifiers/            по файлу на класифікатор
│   ├── enterprise-activity--jk1apy.yaml
│   └── f-sectors--cbbfj4.yaml
├── properties/            налаштування моделі (потоки /properties/*)
├── attachments/           вкладення користувача (звіти, файли)
├── streams/               інші потоки, якщо трапляться
├── .gitignore             містить рядок «.local/»
└── .local/                стан інтерфейсу; у сховище версій не йде

Каталог із програмою не пов’язати засобами робочого столу, а файл — можна, тому точка входу — project.ramus: саме він реєструється як тип файлу application/x-ramus-project. Застосунок приймає і каталог, і цей файл усюди, де очікується шлях до проєкту: у діалозі відкриття, у рядку запуску, у переліку останніх файлів.

Розширення каталогу .ramus — для ока; застосунок його не вимагає, ознакою проєкту є наявність project.ramus усередині.

Проєкт можна тримати в git прямо як є. Збереження без змін не чіпає жодного файлу, тож git status мовчить, доки модель справді не змінилася.

Формат розрахований і на агента

Модель читається, обмірковується й правиться звичайними текстовими засобами — без розбору бінарника й без запущеного застосунку. Що робить це надійним:

Цей документ придатний, щоб віддати його агенту як контекст цілком. Коротший робочий протокол — що читати перед правкою, як перевірити результат і де зупинитися — у AGENT_GUIDE.md.


2. Синтаксис: діалект YAML

YAML 1.2, кодування UTF-8. Правила, яких дотримується запис — і яких варто дотримуватися при ручній правці:

Правило Навіщо
блоковий стиль, відступ мап 2 пробіли рядкові зміни видно в diff; елементи списку — на рівні свого ключа
рядки завжди в одинарних лапках немає «норвезької проблеми»: 'no', 'on', '1.0' лишаються рядками
багаторядковий текст — літеральним блоком \| у diff видно змінений рядок, а не весь абзац
числа й true/false — без лапок тип видно з написання
ключі — без лапок, якщо безпечні F_BOUNDS: замість 'F_BOUNDS':
без якорів і псевдонімів (&, *) однакове значення у двох місцях лишається двома значеннями
без перенесення довгих рядків рядок у файлі = рядок значення
ключі мап відсортовані порядок не залежить від того, як лягла пам’ять
дублікати ключів — помилка читання помилка не проходить мовчки

Читання свідоме: використовується Load зі snakeyaml-engine, який не вміє створювати довільні Java-об’єкти. Тег !! із чужого файлу не перетвориться на виконання коду.

Правити руками можна вільніше, ніж пише сам застосунок: подвійні лапки, відсутність лапок там, де значення однозначне, інший порядок ключів — усе це прочитається. Наступне збереження зведе файл до канонічного вигляду.


3. Ідентифікатори

Кожен класифікатор, атрибут і елемент має ідентифікатор із шести символів: jk1apy, mva0fy, 442a84.

Це не випадковий рядок і не хеш вмісту, а оборотне перетворення числового ключа в базі: мережа Фейстеля на 30 біт, алфавіт 0-9a-z без i, l, o, u (щоб не плутати з 1 і 0). Звідси властивості:

Імена файлів класифікаторів — <читабельна-назва>--<ідентифікатор>.yaml. Перейменування класифікатора змінює ім’я файлу, ідентифікатор — ні.

Правило для правок: ідентифікатори не вигадують. Новий елемент простіше додати в застосунку, а вже потім правити його значення у файлі.


4. Посилання на атрибути

Атрибути адресуються за іменем: F_BOUNDS, Name, Опис. Так файл читається без словника.

Якщо в проєкті два атрибути з однаковим іменем, обидва отримують суфікс: Name#a3f2c1. Правило локальне для проєкту й обчислюється при записі, тож однакові імена в різних проєктах на файли не впливають.

Відповідність «посилання → атрибут» задає attributes.yaml; читач бере її звідти, а не розбирає рядок, тому суфікс — просто частина ключа.

Класифікатори й елементи посилаються одне на одного ідентифікаторами, не іменами: усередині значень посилання машинні, і прив’язувати їх до назв, які користувач змінює щодня, було б крихко.


5. project.ramus

schema: 4
application: 'Ramus'
application-version: '2.0.2'
minimum-version: '2.0'
plugins:
- 'Attribute.IDEF0.Sector'
- 'IDEF0'
sequences:
  crosspoint_sequence: 31
  ordinates__sequence: 1

Перелік plugins тут скорочено: у справжньому проєкті їх десятки, від Attribute.Core.Date до IDEF0, і всі відсортовані.

Поле Що це
schema версія розкладки файлів; читач відмовляється від чужої
application назва застосунку, який записав
application-version його версія; міток часу немає навмисно
minimum-version нижче цієї версії застосунок відмовиться відкривати
plugins плагіни, без яких проєкт не відкрити
sequences лічильники плагінів

plugins — та сама перевірка, що й у .rsf: проєкт, який посилається на невідомий плагін, краще не відкривати взагалі, ніж відкрити з мовчазною втратою даних, якими плагін завідував.

sequences містить лише лічильники плагінів (номери перетинів стрілок, напрямних). Ключі основних таблиць рушій відновлює сам, піднімаючи лічильник над найбільшим наявним ключем.


6. attributes.yaml

schema: 4
attributes:
- ref: 'QualifierAttributes'
  id: '460xra'
  name: 'QualifierAttributes'
  type: 'Core.ElementList'
  system: true
  properties:
    qualifier1: 'geq786'
    qualifier2: '0140fv'
- ref: 'Name'
  id: 'gsjy4v'
  name: 'Name'
  type: 'Core.Text'
  comparable: true
Поле Що це
ref як на цей атрибут посилаються всі інші файли
id ідентифікатор, з якого відновлюється числовий ключ
name показувана назва
type Плагін.Тип — визначає, який плагін розуміє значення
comparable лише коли true: за атрибутом можна впорядковувати
system лише коли true: атрибут створює плагін, а не користувач
properties конфігурація плагіна для цього атрибута

properties — не декорація. Для Core.ElementList там записано, які саме класифікатори зв’язує атрибут; без цього значення елементів не відновити. Для Core.Variant — перелік допустимих варіантів. Перелік типів, що мають властивості, — у розділі 9.

Атрибути перелічені в порядку зростання числового ключа, тобто в порядку створення.


7. qualifiers/*.yaml

schema: 4
id: 'jk1apy'
name: 'Enterprise activity'
name-attribute: 'Name'
attributes:
- 'Name'
- 'Description'
system-attributes:
- 'F_BOUNDS'
- 'F_FONT'
- 'HierarchicalAttribute'
elements:
- id: 'mva0fy'
  name: 'Enterprise activity'
  values:
    F_BOUNDS:
      height: 80.0
      width: 144.0
      x: 324.0
      y: 180.0
    F_FONT:
      name: 'Dialog'
      size: 12
      style: 0
    Name: 'Enterprise activity'

Списки system-attributes і values тут скорочено — справжній елемент несе ще F_BACKGROUND, F_TYPE, дати ревізії й інше.

Поле Що це
id ідентифікатор класифікатора
name назва
system лише коли true: класифікатор створює плагін
name-attribute який атрибут вважається назвою елемента
attributes стовпчики класифікатора, у порядку показу
system-attributes атрибути, які додає плагін; порядок несуттєвий, тому відсортовані
elements[].id ідентифікатор елемента
elements[].name назва в таблиці елементів; здебільшого порожня й тоді відсутня
elements[].values значення, ключі відсортовані за посиланням на атрибут

Порядок attributes значущий — це порядок стовпчиків у таблиці. Порядок system-attributes і elements — ні; елементи впорядковані за ключем, щоб файл не змінювався без потреби.

Класифікатор із system: true створює плагін. Такі не варто ні видаляти, ні перейменовувати: у них лежать базові функції моделей IDEF0, звіти, сектори й дзеркала метаданих рушія — див. розділ 10.


8. Як записані значення

Плагін атрибута зберігає значення як набір рядків, іноді в кількох таблицях. Формат подає найкоротшу з можливих форм:

Випадок Вигляд Приклад
одне змістове поле, один рядок скаляр Name: 'Опис'
кілька полів, один рядок мапа F_BOUNDS: { x: 1.0, … }
кілька рядків список мап F_SECTOR_POINTS: [{…}, {…}]
кілька таблиць список списків F_PROJECT_PREFERENCES: [[…], […]]

Які типи згортаються до скаляра — у розділі 9.

Порожні поля не пишуться, і відсутність поля означає «значення немає» — не типове значення класу. Тому дописане вручну поле з’явиться в моделі, а видалене — зникне. Це не дрібниця: наприклад, у SectorPersistent alternativeText: '' і відсутній alternativeText — різні стани, від яких залежить розкладка підписів стрілок.

Типи скалярів:

Тип поля Запис Приклад
text рядок в одинарних лапках 'Реализованная продукция'
long, integer ціле -16711936
double дробове 80.0
date ISO-8601 UTC '2009-09-03T11:36:00.000Z'
binary base64 'AQAAAB0AAAD…'
element, qualifier, attribute ідентифікатор або -1 '2np969', -1

-1 у полі-посиланні означає «посилання немає». Посилання на сутність, якої в проєкті немає, при записі теж перетворюється на -1: у старих файлах трапляються ключі видалених елементів, і формат не вигадує їм адресата.

Поля elementId, attributeId, valueBranchId у файл не потрапляють — вони задані контекстом, у якому значення записане.


9. Довідник типів атрибутів

Схема виведена з анотацій самих персистентів, тому новий тип атрибута з’являється тут автоматично, без правок серіалізатора. Звірити з кодом — розділ 19.

Core

Тип Значення Властивості
Core.Text скаляр value : text
Core.Long скаляр value : long
Core.Double скаляр value : double
Core.Boolean скаляр value : integer
Core.Date скаляр value : date
Core.Currency скаляр value : double attribute, code : text
Core.Price position : integer, startDate : date, value : double
Core.Variant скаляр variantId : long attribute, position, value : text, variantId
Core.OtherElement скаляр otherElement : element attribute, qualifier, qualifierAttribute
Core.ElementList element1Id : element, element2Id : element, connectionType : text qualifier1, qualifier2, connectionTypes : text
Core.Hierarchical parentElementId : element, previousElementId : element, iconId : long
Core.Icon name : text, icon : binary
Core.File name, path, lastModifiedTime, uploadTime
Core.HTMLText немає — вміст лежить у потоці
Core.Table attribute, otherAttribute, name, subName

⚙ — системний тип.

Core.File і Core.HTMLText тримають самі байти не в таблиці, а в потоці за шляхом /elements/<елемент>/<атрибут>/Core/<ім'я>; у проєкті це файл під attachments/. У Core.File персистент описує вкладення (ім’я, час), у Core.HTMLText персистента немає взагалі.

Core.ElementList — зв’язок «багато до багатьох» між двома класифікаторами. Один зв’язок належить обом кінцям одразу, тому у файлі він трапляється двічі: у значеннях елемента з qualifier1 і елемента з qualifier2. Читач другий запис відкидає. Дублювати вручну не потрібно, але й не шкідливо.

IDEF0

Тип Значення
IDEF0.FRectangle x, y, width, height : double
IDEF0.Font name : text, size : integer, style : integer
IDEF0.Color скаляр color : integer (ARGB, як Color.getRGB())
IDEF0.Type скаляр type : integer
IDEF0.DecompositionType скаляр type : integer
IDEF0.OunerId скаляр ounerId : long
IDEF0.Status type : integer, otherName : text
IDEF0.AnyToAny otherElement : element, elementStatus : text
IDEF0.DFDSName shortName : text, longName : text
IDEF0.TextLabel position, text, x, y, width, height, fontName, fontStyle, fontSize, color
IDEF0.Sector вигляд стрілки, див. нижче
IDEF0.SectorBorder borderType, functionType : integer, function : element, crosspoint : long, tunnelSoft : integer
IDEF0.SectorPoint position : integer, pointType : integer, xPosition, yPosition : double, xOrdinateId, yOrdinateId : long
IDEF0.SectorProperties showText, showTilda, transparent : integer, textX, textY, textWidth, textHieght, tildaPos : double
IDEF0.ProjectPreferences дві таблиці: налаштування моделі та перелік читачів
IDEF0.VisualData скаляр data : binary — див. межі
IDEF0.Sector — вигляд стрілки: alternativeText : text, showText,
textAligment, createState : integer, createPos : double, лінія
(strokeKind : text, strokeWidth, strokeDashPhase, `strokeMiterLimit :
double, strokeEndCap, strokeLineJoin, strokeType : integer, strokeDash
text — довжини штрихів через кому), шрифт (fontName, fontStyle, fontSize) і color : integer. Поле visualAttributes : binary` — застаріле: воно лишається заповненим у моделях, збережених до винесення вигляду в іменовані поля, і читається лише тоді, коли іменованих полів немає.

IDEF0.ProjectPreferences — єдиний тип із двома таблицями, тож його значення має вигляд списку списків: зовнішній список — таблиці, внутрішній — рядки. Схематично (у зразках із dest/doc це значення не задане, тому справжнього прикладу навести нема з чого):

F_PROJECT_PREFERENCES:
- - projectName: 'Діяльність підприємства'
    projectAutor: 'Іванов'
    createDate: '2009-09-03T11:36:00.000Z'
    modelLetter: 'A'
- - reader: 'Петров'
    date: '2009-09-10T08:00:00.000Z'

Порожня таблиця дає порожній список [] на своєму місці.

Eval, Chart

Тип Значення
Eval.Function function : text (вираз), autochange : integer, qualifierAttributeId, qualifierTableAttributeId : long
Chart.Link скаляр otherElementId : element
Chart.TableChart властивість: скаляр otherElementId : element

10. Модель IDEF0 у цих файлах

У Ramus немає окремої «таблиці діаграм»: діаграма — це елементи системних класифікаторів. Знати, який класифікатор за що відповідає, необхідно, щоб правити модель у файлах.

Класифікатор Що тримає
F_BASE_FUNCTIONS по одному елементу на модель — корінь дерева функцій
F_MODEL_TREE дерево моделей проєкту
F_SECTORS стрілки: по елементу на сектор
F_STREAMS потоки — те, що стрілки переносять; назва стрілки живе тут
F_REPORTS_QUALIFIER звіти; їхні тіла — у attachments/
IconsQualifier піктограми
QUALIFIER_EVAL_FUNCTION_DEPENDENCES залежності обчислюваних атрибутів
QUALIFIER_CHARTS, QUALIFIER_CHART_SETS, QUALIFIER_CHART_LINKS діаграми модуля Chart
PLAN_LIST плани модуля планування
QualifiersQualifier, AttributesQualifier дзеркала метаданих рушія: по елементу на класифікатор і на атрибут
HistoryQualifier залишок механізму гілок; лишається без елементів

Дзеркала (QualifiersQualifier, AttributesQualifier) містять по елементу на кожен класифікатор і на кожен атрибут проєкту. Вони потрібні, щоб на класифікатор чи атрибут можна було послатися як на звичайний елемент — саме через них працює, наприклад, Core.ElementList між класифікаторами. Правити їх руками не варто: рушій узгоджує їх сам.

Функційні блоки — це елементи звичайного, користувацького класифікатора моделі (у прикладі — Enterprise activity). Кожен блок несе:

Атрибут Що це
F_BOUNDS прямокутник блока на діаграмі
F_FONT, F_BACKGROUND, F_FOREGROUND вигляд
F_TYPE, F_DECOMPOSITION_TYPE тип блока й тип декомпозиції
F_STATUS стан («чернетка», «затверджено», …)
F_AUTHOR, F_CREATE_DATE, F_REV_DATE, F_SYSTEM_REV_DATE хто й коли
F_OUNER_ID, F_LINK власник і посилання
F_PAGE_SIZE розмір сторінки діаграми
F_TEXT_LABELS вільні підписи на діаграмі декомпозиції
F_VISUAL_DATA залишок старого блоба, див. межі
HierarchicalAttribute місце в дереві: батько й попередник

Стрілка — елемент F_SECTORS з такими атрибутами:

Атрибут Що це
F_FUNCTION_SECTOR на якій діаграмі (елемент функції) стрілка намальована
F_SECTOR_STREAM який потік переносить (елемент F_STREAMS)
F_SECTOR_BORDER_START / _END до чого прикріплені кінці
F_SECTOR_POINTS ламана: точки в порядку position
F_SECTOR_ATTRIBUTE вигляд лінії, шрифт, колір
F_SECTOR_PROPERTIES розташування підпису

Кінець стрілки (SectorBorderPersistent) описаний так:

У нотації IDEF0 сторона й задає роль: ліворуч — вхід, зверху — керування, праворуч — вихід, знизу — механізм. Окремого поля «роль» немає — це причина, чому розкладку ще не відділено від семантики (етап 4 плану).

Точка ламаної (SectorPointPersistent) має і власні координати (xPosition, yPosition), і посилання на спільні напрямні (xOrdinateId, yOrdinateId). Напрямна — це вертикаль або горизонталь, на якій сидять точки кількох стрілок: посунувши її, застосунок рухає їх разом. pointType — орієнтація напрямної, що виходить із точки: 0 — паралельно осі x, 1 — осі y.


11. Потоки

Дані, що живуть поза таблицями, описані в streams.yaml:

schema: 4
properties:
- path: 'idef0.xml'
  file: 'properties/idef0.xml'
attachments:
- element: 'hqw94n'
  attribute: 'F_REPORT_NAME'
  name: 'report.0.xml'
  file: 'attachments/hqw94n/F_REPORT_NAME/report.0.xml'
other:
- path: '/elements/769/17/report.0.xml'
  file: 'streams/elements/769/17/report.0.xml'

file каже, де лежить, решта полів — куди прив’язати при відкритті. Файл можна перейменувати, якщо виправити file. Символи, неприйнятні в іменах файлів, у шляху замінені дефісом — тому name зберігається окремо й відновлює початковий шлях точно.


12. .local/ — стан інтерфейсу

Розкладка вікон, відкриті вкладки, словник перевірки орфографії — усе, що надходило потоками /user/*. Змінюється від кожного кліку й персональне, тому лежить окремо, а .gitignore проєкту його виключає.

.gitignore при збереженні доповнюється, а не переписується: це файл користувача, і його власні правила лишаються на місці.

Втрата .local/ не втрачає нічого з моделі.


13. Чого у проєкті немає

Історії та гілок. Їх веде сховище версій: git log замість таблиць branches, attributes_history, qualifiers_history. Проєкт завжди описує один стан. Журнал скасування живе лише в межах сеансу.

application_preferencies. Ця таблиця в коді нічим не читається й не пишеться — переносити її означало б тягнути мертвий вантаж.

Міток часу збереження. Вони робили б кожне збереження унікальним, і git показував би зміну там, де модель не змінювалася. Дати всередині моделі (F_REV_DATE тощо) — інша річ, вони частина даних і зберігаються.


14. Рецепти безпечного редагування

Загальне правило: правити значення — безпечно, вигадувати ідентифікатори — ні. Створення й видалення сутностей краще робити в застосунку, а у файлах уточнювати вміст.

Перейменувати функційний блок. Знайти елемент у файлі класифікатора моделі, змінити значення атрибута-назви (name-attribute каже, який це). Поле elements[].name міняти не треба — воно не показується.

Посунути блок. F_BOUNDS того самого елемента: x, y — лівий верхній кут, width, height — розмір. Стрілки, прикріплені до блока, застосунок перерахує при відкритті не повністю: точки ламаної лишаться там, де були.

Змінити колір або шрифт. F_BACKGROUND, F_FOREGROUND — ціле ARGB (-16711936 — зелений). F_FONT — мапа з name, size, style.

Перейменувати стрілку. Назва живе не в стрілці, а в потоці: знайти елемент F_SECTORS, узяти F_SECTOR_STREAM, за цим ідентифікатором знайти елемент у F_STREAMS і змінити F_STREAM_NAME. Один потік може обслуговувати кілька стрілок — тоді перейменуються всі.

Додати підпис на діаграму. У списку F_TEXT_LABELS елемента функції дописати запис із position на одиницю більшим за останній, text, x, y, width, height. position має бути унікальним: це ключ, і два підписи з однаковим значенням злипнуться в один.

Масова заміна тексту. Звичайний sed по qualifiers/*.yaml працює, бо рядки не переносяться і не екрануються складно. Після заміни варто відкрити проєкт у застосунку й зберегти — файли зведуться до канонічного вигляду.

Перевірити правку. Відкрити проєкт і зберегти. Якщо git diff показує лише те, що ви змінювали, — правка коректна; якщо файл перебудувався несподівано, значення було записане у формі, яку читач зрозумів інакше.

Чого не варто робити: видаляти системні класифікатори або їхні елементи, міняти id будь-де, знімати system: true, правити sequences вниз.


15. Версія схеми й сумісність

schema: 4 — версія розкладки файлів. Читач відмовляється відкривати іншу версію, а не намагається вгадати. Механізму міграції між версіями схеми поки немає: він з’явиться разом із першою несумісною зміною.

minimum-version і plugins описують вимоги до застосунку, а не до формату: перше порівнюється з версією застосунку, друге — з переліком підключених плагінів.

Проєкт, збережений цією версією, старішим Ramus не відкриється — вона не знає ні каталогів проєкту, ні плагіна Attribute.IDEF0.TextLabel.


16. Старий формат .rsf

.rsf — ZIP-архів із XML-дампами таблиць бази. Відкривається, але не записується. Відкритий .rsf не перезаписується: перше збереження веде до діалогу з іменем нового проєкту, тобто перехід відбувається явно й видимо.

Пакетне перетворення без застосунку:

./gradlew :ramus-core-demo:rsfToYaml -Prsf=<файл.rsf> -Pout=<каталог>
./gradlew :ramus-core-demo:yamlToRsf -Pin=<каталог> -Prsf=<файл.rsf>
`

Зворотний конвертер потрібен лише для обміну зі старими збірками.

.rsf лишається і як внутрішній транспорт там, де він ніколи не був форматом зберігання: обмін класифікаторами між файлами, серверні резервні копії, передача дампа мережевому клієнту.


17. Межі формату

Типи атрибутів, чий плагін не підключений, не переносяться. У деяких старих зразках трапляються атрибути типу Season.PlanField, а плагіна Season у коді немає. .rsf зберігав такі таблиці як непрозорий дамп; новий формат записує значення через плагін, тож без плагіна їх нема чим прочитати. Конвертер повідомляє про кожен такий атрибут.

F_VISUAL_DATA у старих моделях лишається блобом. Геометрія стрілок переїхала в таблиці (версія блоба 3), але файл, збережений раніше, несе її у двійковому полі, доки застосунок не перезбереже відповідну діаграму. Після цього поле скорочується до позначки версії. Окрема давня вада: перезбереження кореневої діаграми файлу першої версії псує граничні стрілки — вона існувала до цих змін, але саме на шляху міграції й спрацьовує.

Розкладку ще не відділено від семантики. Координати лежать поруч зі зв’язками, у тих самих значеннях атрибутів, а роль стрілки виводиться зі сторони блока, а не задана явно. Розділення — етап 4 плану.

Кілька полів-посилань лишаються числами. Вони оголошені як long, хоча зберігають ключі сутностей. Найпомітніші:

Поле На що вказує
F_BASE_FUNCTION_QUALIFIER_ID класифікатор моделі
F_LINK елемент, на який посилається блок
HierarchicalPersistent.iconId елемент IconsQualifier
Eval.Function.qualifierAttributeId атрибут
crosspoint у кінці стрілки вузол розгалуження (не елемент)

Ті, що однозначно вказують на елементи й мають власний клас персистента, вже переведені на ідентифікатори. F_BASE_FUNCTION_QUALIFIER_ID і F_LINK — принципово інший випадок: обидва мають тип Core.Long, тобто користуються тим самим LongPersistent, що й будь-яке число в моделі. Перевести їх можна лише правилом на рівні атрибута, а не персистента, — цього поки немає.

Практичний наслідок: у файлі ці поля виглядають як звичайні числа, і при ручній правці треба пам’ятати, що це числовий ключ бази, а не ідентифікатор із шести символів. Цикл збереження їх не псує — ключі відновлюються точно.


18. Що перевірено

Модуль storage-test і тести local-client:

Перевірка Тест
проєкт → .rsf → проєкт дає ті самі файли ProjectRoundTripTest.projectSurvivesRsfRoundTrip
відкрити й зберегти без змін не чіпає жодного файлу resavingProjectChangesNothing
діаграми після циклу малюються так само diagramsLookIdenticalAfterRoundTrip
проєкт відкривається за project.ramus projectOpensByItsDescriptionFile
стан інтерфейсу не потрапляє у версійовану частину interfaceStateGoesToLocalDirectory
.gitignore користувача не переписується там само
новий елемент не займає чужого ключа ProjectEditingTest.newElementDoesNotReuseExistingKey
правка переживає збереження й відкриття editedValueSurvivesSaveAndReopen
порожній проєкт зберігається й відкривається freshProjectSavesAndOpens
знімок для відновлення після збою не обнуляється ProjectRecoveryTest
шлях користувача перетворюється на каталог проєкту local-client: ProjectNamingTest
вигляд 56 діаграм не змінився DiagramGoldenTest.diagramsMatchGolden
знімок не залежить від шрифтів машини й дня DiagramDeterminismTest
правила емітера YAML YamlFormatTest
ідентифікатори оборотні StableIdsTest

Порівняння діаграм — точне, без допуску: відмальовування детерміноване, а дати й шрифти зафіксовані в самому тесті.

Чого не перевірено: жодної дії в графічному інтерфейсі. Застосунок запускається й відкриває каталог проєкту, але «Зберегти», діалог «Зберегти як» і відтворення журналу поверх знімка проходилися лише на рівні рушія.


19. Як звірити цю доку з кодом

Схема типів атрибутів виводиться з анотацій персистентів, тому її можна надрукувати з коду й порівняти з розділом 9:

./gradlew :storage-test:test --tests '*DumpSchemaTest*' \
    -Dramus.schema.target=/tmp/schema.txt

Подивитися, як виглядає справжній проєкт:

./gradlew :ramus-core-demo:rsfToYaml \
    -Prsf="dest/doc/en/Enterprise activity.rsf" -Pout=/tmp/Enterprise.ramus