Skip to content

Commit 73e5d41

Browse files
feat: add Russian real-world FTS examples
1 parent 18b4a3b commit 73e5d41

24 files changed

Lines changed: 600 additions & 7 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ All authored source uses `.fts`. JSON is the canonical interchange form for APIs
8787

8888
See [Language reference](docs/language.md), [Architecture](docs/architecture.md), [How it works](docs/how-it-works.md), and [Application adoption](docs/adoption.md).
8989

90+
For runnable Russian examples of form generators, table configuration, and DDD command guards, see [FTS на прикладных примерах](docs/examples.ru.md).
91+
9092
## Status
9193

9294
`0.x` is the language-design phase. The canonical JSON shape and diagnostic codes are treated as compatibility surfaces; syntax may grow through documented proposals.

docs/examples.ru.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# FTS на прикладных примерах
2+
3+
Этот раздел показывает механику FTS на формах, таблицах и бизнес-правилах DDD. Все примеры исполняются и входят в автоматические тесты репозитория.
4+
5+
## Ментальная модель
6+
7+
У сценария есть три артефакта:
8+
9+
1. `.fts` описывает форму данных, типизированное правило и проверяемое утверждение;
10+
2. JSON-контекст содержит конкретный снимок прикладных данных;
11+
3. proof certificate связывает модель, контекст, шаги вывода и заключение дайджестами.
12+
13+
FTS не рисует интерфейс и не выполняет доменную команду сам. Утилита читает канонический `FtsDocument` и решает, что построить: форму, таблицу, документацию, валидатор, граф или command guard.
14+
15+
## 1. Классическая форма регистрации клиента
16+
17+
Модель [`customer-onboarding.fts`](../examples/real-world/customer-onboarding.fts):
18+
19+
```fts
20+
category РегистрацияКлиента {
21+
structure АнкетаКлиента {
22+
ид: string
23+
полноеИмя: string
24+
электроннаяПочта: Email
25+
телефон?: Телефон
26+
типКлиента: ТипКлиента
27+
согласиеНаОбработку: СогласиеПолучено
28+
}
29+
30+
functor согласиеРазрешаетРегистрацию: СогласиеПолучено -> РегистрацияРазрешена
31+
32+
proposition apply согласиеРазрешаетРегистрацию {
33+
witness АнкетаКлиента.согласиеНаОбработку {
34+
value true
35+
path ["анкеты", { ид: "КЛ-1042" }, "согласиеНаОбработку"]
36+
}
37+
}
38+
}
39+
```
40+
41+
Утилита [`form-schema.mjs`](../examples/utilities/form-schema.mjs) читает структуру и переводит доменные типы в элементы формы:
42+
43+
```bash
44+
npm run build
45+
node examples/utilities/form-schema.mjs \
46+
examples/real-world/customer-onboarding.fts \
47+
АнкетаКлиента
48+
```
49+
50+
Фрагмент результата:
51+
52+
```json
53+
{
54+
"kind": "form",
55+
"id": "РегистрацияКлиента.АнкетаКлиента",
56+
"fields": [
57+
{
58+
"name": "электроннаяПочта",
59+
"label": "Электронная Почта",
60+
"control": "email",
61+
"required": true,
62+
"domainType": "Email"
63+
},
64+
{
65+
"name": "телефон",
66+
"control": "tel",
67+
"required": false,
68+
"domainType": "Телефон"
69+
}
70+
]
71+
}
72+
```
73+
74+
Механика простая: `structure` является стабильным входом генератора. В production-утилите поверх него можно добавить каталог локализации, layout, маски ввода и дизайн-систему. Эти UI-настройки не должны менять доказательное ядро.
75+
76+
## 2. Код таблицы счетов
77+
78+
Модель [`invoices-table.fts`](../examples/real-world/invoices-table.fts) описывает строку реестра и правило эскалации просроченного счёта. Утилита [`table-columns.mjs`](../examples/utilities/table-columns.mjs) превращает поля в конфигурацию таблицы:
79+
80+
```bash
81+
node examples/utilities/table-columns.mjs \
82+
examples/real-world/invoices-table.fts \
83+
СтрокаСчёта
84+
```
85+
86+
```json
87+
{
88+
"kind": "table",
89+
"id": "ДебиторскаяЗадолженность.СтрокаСчёта",
90+
"columns": [
91+
{ "key": "номер", "header": "Номер", "align": "start", "format": "text" },
92+
{ "key": "сумма", "header": "Сумма", "align": "end", "format": "number" },
93+
{ "key": "просрочен", "header": "Просрочен", "align": "center", "format": "badge" }
94+
]
95+
}
96+
```
97+
98+
Один и тот же `FtsDocument` можно использовать для React/Vue-таблицы, CSV-экспорта, SQL-проекции, документации API или настройки агента. Конкретная утилита импортирует публичный API, а не внутренние токены парсера:
99+
100+
```js
101+
import { assertValid, compile } from "@digitable/fts"
102+
103+
const document = assertValid(compile(source))
104+
const row = document.structures.find((item) => item.name === "СтрокаСчёта")
105+
const columns = row.fields.map((field) => ({
106+
key: field.name,
107+
domainType: field.type,
108+
}))
109+
```
110+
111+
## 3. DDD: защита команды агрегата
112+
113+
В [`order-shipment.fts`](../examples/real-world/order-shipment.fts) агрегат `Заказ` публикует факт `готовКОтгрузке`. Доменный сервис вычисляет этот факт из оплаты, резерва, блокировок и других инвариантов. FTS проверяет конкретный snapshot и типизированный переход к команде:
114+
115+
```fts
116+
functor готовностьРазрешаетКоманду: ГотовКОтгрузке -> ОтгрузитьЗаказРазрешено
117+
118+
proposition apply готовностьРазрешаетКоманду {
119+
witness Заказ.готовКОтгрузке {
120+
value true
121+
path ["заказы", { номер: "ЗК-7781" }, "готовКОтгрузке"]
122+
}
123+
}
124+
```
125+
126+
Запуск command guard:
127+
128+
```bash
129+
node examples/utilities/command-guard.mjs \
130+
examples/real-world/order-shipment.fts \
131+
examples/real-world/order-shipment.context.json
132+
```
133+
134+
```json
135+
{
136+
"allowed": true,
137+
"command": "ОтгрузитьЗаказРазрешено",
138+
"proofTerm": "готовностьРазрешаетКоманду(witness(Заказ.готовКОтгрузке))",
139+
"assumptions": [
140+
"готовностьРазрешаетКоманду : ГотовКОтгрузке → ОтгрузитьЗаказРазрешено [functor.arrow]"
141+
]
142+
}
143+
```
144+
145+
Критическая граница: FTS строго доказывает соответствие witness снимку данных, целостность цепочки типов и неизменность сертификата. Закон `готовностьРазрешаетКоманду` пока является явно записанной предпосылкой. Если нужно доказать сам расчёт `готовКОтгрузке`, его следует выразить отдельным проверяемым правилом или приложить сертификат нижнего уровня.
146+
147+
Для [`order-shipment.blocked.context.json`](../examples/real-world/order-shipment.blocked.context.json), где склад не подтвердил резерв, та же утилита возвращает `{"allowed": false, ...}` и команда не вызывается.
148+
149+
## 4. DDD: композиция политики
150+
151+
[`credit-limit.fts`](../examples/real-world/credit-limit.fts) демонстрирует цепочку:
152+
153+
```text
154+
СкорингПройден
155+
-> РискПроверкаРазрешена
156+
-> ЛимитМожетБытьУстановлен
157+
```
158+
159+
```fts
160+
proposition compose {
161+
functors: ["скорингОткрываетРискПроверку", "рискПроверкаРазрешаетЛимит"]
162+
witness ЗаявкаНаЛимит.скорингПройден {
163+
value true
164+
path ["заявки", { номер: "ЛМ-205" }, "скорингПройден"]
165+
}
166+
}
167+
```
168+
169+
Если поменять порядок функторов или их доменные типы, `fts certify` отклонит цепочку. Если изменить snapshot после сертификации, `fts verify` отклонит digest.
170+
171+
## 5. Использование из TypeScript
172+
173+
```ts
174+
import { assertVerified, certify, compile } from "@digitable/fts"
175+
176+
const document = compile(source)
177+
const certificate = certify(document, context)
178+
const verification = assertVerified(document, certificate, context)
179+
180+
if (verification.valid && verification.status === "verified") {
181+
await commandBus.execute(new ОтгрузитьЗаказ(context.номер))
182+
}
183+
```
184+
185+
Для независимой границы доверия producer и verifier лучше запускать в разных процессах или сервисах. CLI и MCP используют тот же канонический формат.
186+
187+
## 6. Русский язык
188+
189+
Поддерживается:
190+
191+
- русские имена категорий, структур, полей, функторов и типов;
192+
- русские строки, комментарии, object keys, selectors и JSON paths;
193+
- смешанные имена наподобие `клиентVIP`;
194+
- NFC-нормализация идентификаторов;
195+
- воспроизводимые SHA-256 digest для UTF-8 данных.
196+
197+
Ключевые слова FTS пока фиксированы на английском. Это сохраняет одну грамматику и позволяет русским и международным командам обмениваться моделями без трансляции синтаксиса. Диагностические коды стабильны, сообщения v0.1 пока английские; UI может локализовать их по `code`.
198+
199+
## 7. Что уже удобно, а чего пока не хватает
200+
201+
Уже подходит для генераторов форм и таблиц, схем API, документации, графов, DDD command guards, CI-проверок и агентных инструментов.
202+
203+
Для полноценного промышленного form/table DSL ещё нужны декларативные аннотации (`label`, `format`, `widget`, `enum`, `permissions`), импорты и версии моделей. Для более сильной бизнес-логики нужны конъюнкция нескольких witness, кванторы, арифметические предикаты и сертификаты законов функторов. Эти возможности лучше добавлять как расширения канонической модели, сохраняя малое проверяемое ядро.

docs/language.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ separator := newline | ";"
3131

3232
Comments use `// ...` or `/* ... */`. Strings may use single or double quotes. Objects permit identifier keys, which is the only intentional JSON5-like convenience.
3333

34+
Identifiers follow Unicode identifier rules and are normalized to NFC. Russian domain names are valid everywhere an identifier is expected: `Продажи`, `Заказ`, `статусОплаты`, `разрешитьОтгрузку`. Structural keywords (`category`, `structure`, `functor`, `proposition`, `witness`, `apply`, `compose`) remain language-neutral and stable; strings, object keys, paths, details, types, and domain identifiers may be Russian.
35+
3436
## Declarations
3537

3638
`category` is the document boundary and roughly corresponds to a TypeScript namespace.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"заявки": [
3+
{
4+
"номер": "ЛМ-205",
5+
"клиент": "АО Горизонт",
6+
"сумма": 1000000,
7+
"скорингПройден": true,
8+
"решениеРисков": "одобрено"
9+
}
10+
]
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
category КредитнаяПолитика {
2+
structure ЗаявкаНаЛимит {
3+
номер: string
4+
клиент: string
5+
сумма: Money
6+
скорингПройден: СкорингПройден
7+
решениеРисков: РискиОдобрили
8+
}
9+
10+
functor скорингОткрываетРискПроверку: СкорингПройден -> РискПроверкаРазрешена
11+
functor рискПроверкаРазрешаетЛимит: РискПроверкаРазрешена -> ЛимитМожетБытьУстановлен
12+
13+
proposition compose {
14+
functors: ["скорингОткрываетРискПроверку", "рискПроверкаРазрешаетЛимит"]
15+
witness ЗаявкаНаЛимит.скорингПройден {
16+
value true
17+
path ["заявки", { номер: "ЛМ-205" }, "скорингПройден"]
18+
detail "Цепочка политики выдачи кредитного лимита"
19+
}
20+
}
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"анкеты": [
3+
{
4+
"ид": "КЛ-1042",
5+
"полноеИмя": "Анна Сергеевна Волкова",
6+
"электроннаяПочта": "anna.volkova@example.ru",
7+
"телефон": "+7 999 123-45-67",
8+
"типКлиента": "ИП",
9+
"согласиеНаОбработку": true
10+
}
11+
]
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
category РегистрацияКлиента {
2+
structure АнкетаКлиента {
3+
ид: string
4+
полноеИмя: string
5+
электроннаяПочта: Email
6+
телефон?: Телефон
7+
типКлиента: ТипКлиента
8+
согласиеНаОбработку: СогласиеПолучено
9+
}
10+
11+
functor согласиеРазрешаетРегистрацию: СогласиеПолучено -> РегистрацияРазрешена
12+
13+
proposition apply согласиеРазрешаетРегистрацию {
14+
witness АнкетаКлиента.согласиеНаОбработку {
15+
value true
16+
path ["анкеты", { ид: "КЛ-1042" }, "согласиеНаОбработку"]
17+
detail "Клиент явно подтвердил обработку персональных данных"
18+
}
19+
}
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"счета": [
3+
{
4+
"номер": "СЧ-2026-0087",
5+
"контрагент": "ООО Северный Ветер",
6+
"сумма": 245000,
7+
"валюта": "RUB",
8+
"срокОплаты": "2026-07-15",
9+
"просрочен": true
10+
}
11+
]
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
category ДебиторскаяЗадолженность {
2+
structure СтрокаСчёта {
3+
номер: string
4+
контрагент: string
5+
сумма: Money
6+
валюта: Валюта
7+
срокОплаты: Date
8+
просрочен: Просрочен
9+
}
10+
11+
functor просрочкаТребуетКонтроля: Просрочен -> ТребуетсяКонтроль
12+
13+
proposition apply просрочкаТребуетКонтроля {
14+
witness СтрокаСчёта.просрочен {
15+
value true
16+
path ["счета", { номер: "СЧ-2026-0087" }, "просрочен"]
17+
detail "Срок оплаты прошёл, счёт включается в очередь контроля"
18+
}
19+
}
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"заказы": [
3+
{
4+
"номер": "ЗК-7781",
5+
"клиент": "ООО Маяк",
6+
"оплачен": true,
7+
"складПодтвердил": false,
8+
"готовКОтгрузке": false
9+
}
10+
]
11+
}

0 commit comments

Comments
 (0)