Описывает данные и примеры
Пишет структуру формы или таблицы и проверяет ожидаемые результаты как новые предметные unit-тесты.
fts test order.ftsFormal Type Surface · open source
FTS — отступный язык исполняемых спецификаций. Вы описываете предметные данные, правила и примеры без скобок; один .fts становится работающей утилитой, тестами, TypeScript-кодом, UI-описанием или проверяемым допуском команды. А с языком flang тот же файл печатается исходником на JavaScript, C, Go, Rust или Python — и на нём же переписано само ядро FTS.
категория «Продажи»
объект Покупка
сумма является деньгами
утилита «Рассчитать скидку»
принимает Покупка
возвращает деньги
начинает с 0
правило «Большая покупка»
если сумма не меньше 10000
и сумма не больше 100000
то добавить 10 процентов от поля сумма
пример «Двадцать тысяч»
дано сумма равна 20000
ожидается результат равен 2000
Зачем ещё один язык
Обычная спецификация объясняет намерение, а реализация живёт отдельно. FTS делает проверяемую часть правила единым исходником для людей, приложения, тестов и агентов.
FTS хорошо описывает чистые вычисления и проверяемые допуски. Он не генерирует оплату, SQL-транзакцию или HTTP-вызов: приложение выполняет внешний эффект только после результата FTS.
Одна модель — разные уровни работы
Пишет структуру формы или таблицы и проверяет ожидаемые результаты как новые предметные unit-тесты.
fts test order.ftsВызывает FTS из Node.js или генерирует TypeScript, оставляя HTTP, БД и очереди в обычном коде.
fts generate order.ftsОтделяет чистую бизнес-политику от эффектов, вводит command guards, свойства и версионируемые контракты.
fts certify policy.ftsСвязывает DDD-контекст, код, CI и архитектурные решения одним проверяемым артефактом.
fts pipeline domain.ftsАгент пишет FTS и вызывает MCP. Компилятор, тесты и verifier принимают детерминированное решение.
fts_verify → verifiedОдин смысл · две записи
Обе поверхности исполняются одним парсером и нормализуются в одну каноническую модель. Имена с пробелами пишутся в кавычках; обычные кавычки и «ёлочки» поддерживаются.
категория «Продажи»
объект Покупка
сумма является деньгами
«постоянный клиент» является признаком
утилита «Рассчитать скидку»
принимает Покупка
возвращает деньги
начинает с 0
правило «Большая покупка»
если сумма не меньше 10000
и сумма не больше 100000
то добавить 10 процентов от поля сумма
свойство «Скидка ограничена»
результат не больше 15000
пример «Покупка на двадцать тысяч»
дано сумма равна 20000
дано «постоянный клиент» равен нет
ожидается результат равен 2000
category "Sales"
object Purchase
amount is money
"loyal customer" is boolean
utility "Calculate discount"
accepts Purchase
returns money
starts with 0
rule "Large purchase"
if amount is at least 10000
and amount is at most 100000
then add 10 percent of field amount
property "Discount is capped"
result is at most 15000
example "Twenty thousand purchase"
given amount equals 20000
given "loyal customer" equals false
expected result equals 2000
Живая песочница
Здесь работает тот же компилятор @digitable/fts, что и в Node.js: разбор, предметные примеры, выполнение утилиты, генерация TypeScript и доказательство теоремы считаются локально, без единого запроса на сервер.
Компилятор загрузится, когда секция появится на экране.
Компилятор загружается…
Интеграция
import { compile, executeUtility } from "@digitable/fts"
const model = compile(source)
const discount = executeUtility(
model,
"Рассчитать скидку",
{ сумма: 20_000, "постоянный клиент": true }
)
Node отвечает за HTTP и базу; FTS — только за детерминированное правило.
import { compile } from "@digitable/fts/browser"
import { FtsForm } from "@digitable-lol/ui-components"
const document = compile(source)
return <FtsForm
document={document}
objectName="Покупка"
value={value}
onChange={setValue}
/>
Компонент берёт поля из той же модели, которую проверяет backend.
fts run discount.fts \
--utility "Рассчитать скидку" \
--input purchase.json
# stdout: JSON
# failure: non-zero exit code
Python, Go, Java, Rust и shell используют CLI или каноническую JSON-схему.
fts_check
fts_test
fts_execute
fts_certify
fts_verify # status must be verified
Модель может написать текст, но допуск consequential-команды решает verifier.
FTS вырос в язык · flang
Документ, описывающий правило, расходится с реализацией в тот же день, когда его согласовали, и узнают об этом на проде. Поэтому правило здесь не документ: тот самый .fts, который открыт в песочнице выше, исполняется как раньше — и печатается готовым исходником на JavaScript, C, Go, Rust или Python, который ложится в репозиторий проекта и даёт те же значения, те же коды и те же тексты ошибок, что интерпретатор.
Печатает его flang — полный язык, выросший из FTS: суммы типов, коллекции, строки как данные, рекурсия и сопоставление с образцом. Любая модель FTS остаётся валидной программой flang, поэтому переход ничего не ломает: ниже одно и то же правило и его свойство в пяти целевых языках.
правило «Очень крупная покупка»
если сумма больше 100000
то добавить 15000
свойство «Скидка ограничена»
результат не больше 15000
let $t6
if ($cond($gt($field(vhod, "сумма"), 100000))) {
$t6 = $add(rezultat2, 15000)
} else {
$t6 = rezultat2
}
const rezultat3 = $t6
// постусловие «Скидка ограничена»
if (!$post($lte(rezultat3, 15000), "Скидка ограничена", "Рассчитать скидку")) {
$fail("FTS_UTILITY_PROPERTY", "нарушено свойство «Скидка ограничена» утилиты «Рассчитать скидку»")
}
return rezultat3
}
fl_value fl_t26 = fl_nothing();
FL_TRY(fl_field_get(ctx, vhod, "сумма", &fl_t26, error));
fl_value fl_t27 = fl_nothing();
FL_TRY(fl_gt(ctx, fl_t26, fl_number(100000.0), &fl_t27, error));
bool fl_t28 = false;
FL_TRY(fl_cond(ctx, fl_t27, &fl_t28, error));
fl_value fl_t29 = fl_nothing();
if (fl_t28) {
fl_value fl_t30 = fl_nothing();
FL_TRY(fl_add(ctx, rezultat2, fl_number(15000.0), &fl_t30, error));
fl_t29 = fl_t30;
} else {
fl_t29 = rezultat2;
}
const fl_value rezultat3 = fl_t29; /* пусть «результат3» */
(void)rezultat3;
const fl_value fl_t31 = rezultat3;
fl_value fl_t32 = fl_nothing();
FL_TRY(fl_lte(ctx, fl_t31, fl_number(15000.0), &fl_t32, error));
/* постусловие «Скидка ограничена» */
bool fl_t33 = false;
FL_TRY(fl_post(ctx, fl_t32, "Скидка ограничена", "Рассчитать скидку", &fl_t33, error));
if (!fl_t33) {
return fl_fail(ctx, error, "FTS_UTILITY_PROPERTY", "%s", "нарушено свойство «Скидка ограничена» утилиты «Рассчитать скидку»");
}
*result = fl_t31;
return FL_OK;
}
t46, e47 := rt.FieldGet(ctx, vhod, "сумма")
if e47 != nil {
return rt.Value{}, e47
}
t48, e49 := rt.Gt(ctx, t46, rt.Number(100000.0))
if e49 != nil {
return rt.Value{}, e49
}
t50, e51 := rt.Cond(ctx, t48)
if e51 != nil {
return rt.Value{}, e51
}
var t52 rt.Value
if t50 {
t53, e54 := rt.Add(ctx, rezultat2, rt.Number(15000.0))
if e54 != nil {
return rt.Value{}, e54
}
t52 = t53
} else {
t52 = rezultat2
}
// пусть «результат3»
rezultat3 := t52
_ = rezultat3
t55 := rezultat3
t56, e57 := rt.Lte(ctx, t55, rt.Number(15000.0))
if e57 != nil {
return rt.Value{}, e57
}
// постусловие «Скидка ограничена»
t58, e59 := rt.Post(ctx, t56, "Скидка ограничена", "Рассчитать скидку")
if e59 != nil {
return rt.Value{}, e59
}
if !t58 {
return rt.Value{}, rt.Fail("FTS_UTILITY_PROPERTY", "%s", "нарушено свойство «Скидка ограничена» утилиты «Рассчитать скидку»")
}
return t55, nil
}
let t26 = rt::field_get(ctx, vhod.clone(), "сумма")?;
let t27 = rt::gt(ctx, t26, rt::number(100000.0))?;
let t28 = rt::cond(ctx, t27)?;
let t29 = if t28 {
let t30 = rt::add(ctx, rezultat2.clone(), rt::number(15000.0))?;
t30
} else {
rezultat2.clone()
};
// пусть «результат3»
let rezultat3 = t29;
let t31 = rezultat3.clone();
// постусловие «Скидка ограничена»
let t32 = rt::lte(ctx, t31.clone(), rt::number(15000.0))?;
let t33 = rt::post(ctx, t32, "Скидка ограничена", "Рассчитать скидку")?;
if !t33 {
return Err(rt::fail("FTS_UTILITY_PROPERTY", "нарушено свойство «Скидка ограничена» утилиты «Рассчитать скидку»".to_string()));
}
Ok(t31)
}
if rt.cond(ctx, rt.gt(ctx, rt.field_get(ctx, vhod, "сумма"), rt.number(100000.0))):
_t6 = rt.add(ctx, rezultat2, rt.number(15000.0))
else:
_t6 = rezultat2
# пусть «результат3»
rezultat3 = _t6
_t7 = rezultat3
# постусловие «Скидка ограничена»
if not rt.post(ctx, rt.lte(ctx, _t7, rt.number(15000.0)), "Скидка ограничена", "Рассчитать скидку"):
raise rt.fail("FTS_UTILITY_PROPERTY", "нарушено свойство «Скидка ограничена» утилиты «Рассчитать скидку»")
return _t7
Фрагменты напечатаны командой flang emit static/fts/models/order-discount.fts --target js|c|go|rust|python — это вывод компилятора, а не пересказ. Показан хвост одной функции: последнее правило и постусловие. Свойство «Скидка ограничена» — не комментарий и не тест рядом: в каждом языке это исполняемая проверка с кодом FTS_UTILITY_PROPERTY. Напечатанный модуль самодостаточен, рантайм печатается в тот же каталог. Собранный C, собранный Rust, напечатанные JavaScript и Python на входе «сумма 20000, постоянный клиент да» отвечают 3000 — ровно столько же, сколько считает песочница выше.
JavaScript, C, Go, Rust и Python. Каждый бэкенд сверен с интерпретатором дифференциально: 31 программа, 154 функции, 2235 входов — совпадают значения, коды и тексты ошибок.
node --test flang/test/emit-*.test.mjsЛексер, парсер, вычислитель и печать JSON — 300 функций, и все тотальные. Цепочка текст → JSON байт в байт совпадает с эталонным ядром на TypeScript на всём корпусе моделей: и репозитория языка, и этого портала.
текст → лексер → парсер → JSONЯдро печатается в C и собирается cc -Werror -pedantic без единого предупреждения. В зависимостях только libc и libm: ни Node, ни пакетов, ни виртуальной машины.
flang emit core/parser.flang --target cЗавершение тотальной функции доказывает компилятор, а не комментарий рядом. Только такие годятся для факт-чекинга: то, что обязано ответить «подтверждено или нет», не имеет права зависнуть.
FLANG_NOT_TOTAL26 решений на flang: список, число, рекурсия и ни одной предметной области — и та же доказанная завершаемость, что у бизнес-правила.
flang/examples/leetcode/Ограничение записано в самом FTS — tools/ftsc/self/meta.fts: «в ядре FTS строка является типом поля, но не значением, над которым можно вычислять». Парсер — это вычисления над строками, поэтому написать ядро FTS на самом FTS было нельзя. flang снял ровно это ограничение, и первым, что на нём написали, стало ядро FTS.
Что находится при переносе
Не «код стал чище»: правило получило свойства и примеры, и 350 входов прошли через обе реализации. Совпало 317, разошлось 33 — каждое расхождение оказалось ошибкой старого кода. Пример целиком лежит в репозитории: examples/fts/lodash.
Золотой статус, опт и промокод давали 14 % при пределе 13 %. clamp возвращал потолок, и на цене 1234.55 клиент получал скидку 1604.92 — число, которого не описывает ни одно правило.
Округление стояло внутри каждого слагаемого. База 157.50: 7 % = 11.025 и 5 % = 7.875, обе половинки копейки уходили вверх. Итог 18.91 вместо 18.90 — правым оказался расчёт по модели.
Позиция без цены давала итог 0: clamp(NaN, 0, NaN) возвращает нижнюю границу, а defaultTo добивает остаток. Корзина с неоценённым товаром выглядела правдоподобно. Модель останавливается на типе входа.
Полного порта lodash не будет: в языке нет коллекций и функций высшего порядка, поэтому map, filter, groupBy, debounce и cloneDeep остаются в JavaScript и переезжать не должны. В модель уходит другая половина вызовов — та, что на самом деле была бизнес-правилом без проверки.
Разбор целиком — в главе курса «Вам не нужен lodash — но не там, где вы думаете».
Реальные проекты
Это не псевдокод из слайдов. Файлы лежат в репозитории курса, покрыты тестами и запускаются одной командой: npm run fts:examples. Ниже — их настоящее содержимое.
Node.js отвечает за сокет и коды ответа, FTS — за правило. Примеры модели выполняются на старте: если предметный тест не проходит, сервис не поднимается.
npm run fts:apinode --test examples/fts/discount-api/server.test.mjs/**
* HTTP-сервис расчёта скидки поверх FTS.
*
* Разделение ответственности, ради которого всё и затевалось:
* — FTS отвечает за бизнес-правило (какая скидка положена);
* — Node.js отвечает за эффекты (сокет, маршруты, коды ответа, лимит тела).
*
* Примеры из модели выполняются на старте: если предметный тест не проходит,
* сервис не поднимается вообще. Правило, которое не проходит собственные
* примеры, до продакшена не доезжает.
*
* Запуск: node examples/fts/discount-api/server.mjs
* Проверка: curl -s localhost:8788/discount -d '{"сумма":20000,"постоянный клиент":true}'
*/
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { resolve } from 'node:path';
import { assertValid, compile, executeUtility, testUtilities } from '../../../static/js/vendor/fts/browser.js';
const here = fileURLToPath(new URL('.', import.meta.url));
export const MODEL_FILE = resolve(here, '../../../static/fts/models/order-discount.fts');
const UTILITY = 'Рассчитать скидку';
const MAX_BODY = 64 * 1024;
export async function createCalculator(modelFile = MODEL_FILE) {
const document = assertValid(compile(await readFile(modelFile, 'utf8')));
const tests = testUtilities(document);
if (!tests.valid) {
const failed = tests.results.filter((result) => !result.passed);
throw new Error(
`FTS-примеры не прошли (${failed.length} из ${tests.total}): ` +
failed.map((result) => `«${result.example}» ожидалось ${result.expected}, получено ${result.actual}`).join('; '),
);
}
return { document, tests, calculate: (purchase) => executeUtility(document, UTILITY, purchase) };
}
export async function createDiscountServer(modelFile = MODEL_FILE) {
const { calculate, tests, document } = await createCalculator(modelFile);
const fields = document.structures.find((structure) => structure.name === 'Покупка').fields;
return createServer(async (request, response) => {
try {
if (request.method === 'GET' && request.url === '/health') {
return send(response, 200, { ok: true, model: document.category, examples: `${tests.passed}/${tests.total}` });
}
if (request.method === 'GET' && request.url === '/contract') {
return send(response, 200, { utility: UTILITY, input: fields, output: 'Деньги' });
}
if (request.method !== 'POST' || request.url !== '/discount') {
return send(response, 404, { error: 'используйте POST /discount' });
}
const purchase = JSON.parse(await readBody(request));
return send(response, 200, { discount: calculate(purchase) });
} catch (error) {
/* Нарушенное свойство и неизвестное поле — это ошибка запроса, а не сбой
сервиса: клиенту возвращается причина, диагностика FTS не теряется. */
return send(response, 400, {
error: error instanceof Error ? error.message : String(error),
diagnostics: error?.diagnostics ?? [],
});
}
});
}
async function readBody(request) {
const chunks = [];
let size = 0;
for await (const chunk of request) {
size += chunk.length;
if (size > MAX_BODY) throw new Error('тело запроса больше 64 КБ');
chunks.push(chunk);
}
return Buffer.concat(chunks).toString('utf8');
}
function send(response, status, body) {
response.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
response.end(`${JSON.stringify(body, null, 2)}\n`);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const port = Number.parseInt(process.env.FTS_HTTP_PORT ?? '8788', 10);
const server = await createDiscountServer();
server.listen(port, '127.0.0.1', () => {
process.stdout.write(`Discount API: http://127.0.0.1:${port} (POST /discount, GET /contract, GET /health)\n`);
});
}
Модель формулирует утверждение на FTS, а допуск команды выдаёт доказательство на реальных данных заказа. Убедительность текста здесь ничего не решает.
node examples/fts/shipment-guard/guard.mjs ЗК-7781… ЗК-7781 --blocked → exit 1/**
* Допуск команды «отгрузить заказ» через доказательство на реальных данных.
*
* Это шаблон для агентских сценариев: модель (или человек) формулирует
* утверждение на FTS, а решение принимает не текст ответа, а движок. Если факт
* в данных не подтверждается, prove() отклоняет теорему и команда не выполняется.
* Эффект (реальная отгрузка) живёт снаружи и вызывается только после allow.
*
* Запуск: node examples/fts/shipment-guard/guard.mjs ЗК-7781
* node examples/fts/shipment-guard/guard.mjs ЗК-7781 --blocked
*/
import { readFile } from 'node:fs/promises';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { resolve } from 'node:path';
import { compile, prove } from '../../../static/js/vendor/fts/browser.js';
const here = fileURLToPath(new URL('.', import.meta.url));
const models = resolve(here, '../../../static/fts/models');
/* Кавычки внутри имени сломали бы разбор, поэтому номер заказа проверяется
до подстановки: в теорему попадают только допустимые идентификаторы. */
const ORDER_NUMBER = /^[\p{L}\p{N}-]{1,32}$/u;
export function shipmentTheorem(orderNumber) {
if (!ORDER_NUMBER.test(orderNumber)) throw new Error(`недопустимый номер заказа: ${orderNumber}`);
return `категория «Исполнение заказа»
объект Заказ
номер является строкой
клиент является строкой
оплачен является признаком
«склад подтвердил» является признаком
«готов к отгрузке» является состоянием «Готов к отгрузке»
морфизм «Готовый заказ можно отгрузить»
если «Готов к отгрузке»
то «Отгрузить заказ разрешено»
теорема «Заказ ${orderNumber} можно отгрузить»
дано Заказ имеет «готов к отгрузке» равное да
в данных заказы найти где номер равен «${orderNumber}»
по морфизму «Готовый заказ можно отгрузить»
следовательно «Отгрузить заказ разрешено»
`;
}
export function decideShipment(orderNumber, context) {
const source = shipmentTheorem(orderNumber);
try {
const proof = prove(compile(source), context);
return { allowed: true, source, proof };
} catch (error) {
return {
allowed: false,
source,
reason: error instanceof Error ? error.message : String(error),
diagnostics: error?.diagnostics ?? [],
};
}
}
export async function loadContext(blocked = false) {
const file = resolve(models, blocked ? 'order-shipment.blocked.context.json' : 'order-shipment.context.json');
return JSON.parse(await readFile(file, 'utf8'));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const orderNumber = process.argv[2] ?? 'ЗК-7781';
const decision = decideShipment(orderNumber, await loadContext(process.argv.includes('--blocked')));
process.stdout.write(`${JSON.stringify(decision, null, 2)}\n`);
/* Ненулевой код возврата делает guard пригодным для shell и CI. */
process.exit(decision.allowed ? 0 : 1);
}
Из модели получаются реализация и тесты на node:test. Режим --check роняет сборку, если правило поправили в TypeScript мимо модели.
node examples/fts/typescript-codegen/generate.mjs… generate.mjs --check// Generated by FTS. Do not edit by hand.
export interface FtsInput0 {
"сумма": number
"постоянный клиент": boolean
}
export const ftsUtilities = {
"Рассчитать скидку": (input: FtsInput0): number => {
let result: number = 0
if (input["сумма"] >= 10000 && input["сумма"] <= 100000) {
result += (10 / 100) * input["сумма"]
}
if (input["постоянный клиент"] === true && input["сумма"] > 0 && input["сумма"] <= 100000) {
result += (5 / 100) * input["сумма"]
}
if (input["сумма"] > 100000) {
result += 15000
}
if (!(result <= 15000)) throw new Error("Нарушено свойство «Скидка ограничена»")
return result
},
} as const
Схема формы выводится из объекта FTS: контрол, тип, обязательность. Компонент React не дублирует контракт, который уже проверяет бэкенд.
node examples/fts/form-schema/schema.mjs Покупкаиногда является → required: false/**
* Форма скидки на React.
*
* Компонент не знает, что покупка состоит из суммы и признака постоянного
* клиента: поля приходят из той же FTS-модели, которую проверяет бэкенд.
* Добавили в модель поле — форма получила контрол без правки JSX.
*
* Предпросчёт скидки выполняется прямо в браузере тем же компилятором, поэтому
* пользователь видит результат без сетевого запроса. Сервер всё равно считает
* заново: браузеру доверять нельзя, но и ждать его не нужно.
*/
import { useMemo, useState } from 'react';
import { compile, executeUtility } from '@digitable/fts/browser';
import { ftsFormSchema } from '../form-schema/schema.mjs';
export function DiscountForm({ source, objectName = 'Покупка', utility = 'Рассчитать скидку', onSubmit }) {
const document = useMemo(() => compile(source), [source]);
const schema = useMemo(() => ftsFormSchema(document, objectName), [document, objectName]);
const [value, setValue] = useState(() =>
Object.fromEntries(schema.fields.map((field) => [field.name, field.control === 'checkbox' ? false : ''])),
);
const preview = useMemo(() => {
try {
const input = Object.fromEntries(
schema.fields.map((field) => [
field.name,
field.control === 'checkbox' ? Boolean(value[field.name]) : Number(value[field.name] || 0),
]),
);
return { ok: true, amount: executeUtility(document, utility, input) };
} catch (error) {
/* Нарушенное свойство модели — это нормальный ответ формы, а не падение UI. */
return { ok: false, reason: error.message };
}
}, [document, schema, utility, value]);
return (
<form
onSubmit={(event) => {
event.preventDefault();
onSubmit?.(value);
}}
>
<h2>{schema.title}</h2>
{schema.fields.map((field) => (
<label key={field.name}>
<span>{field.label}</span>
<input
type={field.control === 'checkbox' ? 'checkbox' : field.control === 'text' ? 'text' : 'number'}
required={field.required}
checked={field.control === 'checkbox' ? Boolean(value[field.name]) : undefined}
value={field.control === 'checkbox' ? undefined : value[field.name]}
onChange={(event) =>
setValue((current) => ({
...current,
[field.name]: field.control === 'checkbox' ? event.target.checked : event.target.value,
}))
}
/>
</label>
))}
<output>
{preview.ok ? `Скидка: ${preview.amount} ₽` : `Правило не выполняется: ${preview.reason}`}
</output>
<button type="submit" disabled={!preview.ok}>
Оформить
</button>
</form>
);
}
Проверка модели, выполнение примеров, сверка сгенерированного кода и тесты интеграций — четыре шага, которые ловят расхождение между текстом правила и кодом.
.github/workflows/fts-check.ymlnpm run fts:examples# Предметные правила как обычный шаг CI.
#
# Смысл шага не в «ещё одном линтере»: примеры внутри .fts — это тесты,
# написанные на языке предметной области, а сгенерированный TypeScript
# сверяется с моделью. Правило, изменённое в коде мимо модели, ломает сборку.
#
# Файл кладётся в .github/workflows/fts-check.yml
name: fts
on:
push:
branches: [master]
pull_request:
jobs:
rules:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
# Модель разобралась, типы сошлись, ссылки на объекты и поля существуют.
- name: Проверить модели
run: npx fts check static/fts/models/order-discount.fts
# Примеры из .fts выполняются как предметные unit-тесты.
- name: Выполнить предметные примеры
run: npx fts test static/fts/models/order-discount.fts --pretty
# Сгенерированный TypeScript обязан совпадать с закоммиченным.
- name: Сверить сгенерированный код
run: node examples/fts/typescript-codegen/generate.mjs --check
# Интеграции: HTTP-сервис, guard допуска команды, схема формы.
- name: Тесты интеграций
run: node --test "examples/fts/**/*.test.mjs"
Каталог применимости
Это не 232 обещаний «можно автоматизировать». Каждый кейс называет входные факты, роль FTS, выходной артефакт и место интеграции.
Retail: deterministic calculation
Retail: command eligibility
Retail: executable examples
Retail: generated form
Retail: generated table
Retail: verified transition
Retail: agent tool
Retail: regression gate
Marketplaces: deterministic calculation
Marketplaces: command eligibility
Marketplaces: executable examples
Marketplaces: generated form
Marketplaces: generated table
Marketplaces: verified transition
Marketplaces: agent tool
Marketplaces: regression gate
Banking: deterministic calculation
Banking: command eligibility
Banking: executable examples
Banking: generated form
Banking: generated table
Banking: verified transition
Banking: agent tool
Banking: regression gate
Payments: deterministic calculation
Payments: command eligibility
Payments: executable examples
Payments: generated form
Payments: generated table
Payments: verified transition
Payments: agent tool
Payments: regression gate
Insurance: deterministic calculation
Insurance: command eligibility
Insurance: executable examples
Insurance: generated form
Insurance: generated table
Insurance: verified transition
Insurance: agent tool
Insurance: regression gate
Logistics: deterministic calculation
Logistics: command eligibility
Logistics: executable examples
Logistics: generated form
Logistics: generated table
Logistics: verified transition
Logistics: agent tool
Logistics: regression gate
Healthcare: deterministic calculation
Healthcare: command eligibility
Healthcare: executable examples
Healthcare: generated form
Healthcare: generated table
Healthcare: verified transition
Healthcare: agent tool
Healthcare: regression gate
Pharmaceuticals: deterministic calculation
Pharmaceuticals: command eligibility
Pharmaceuticals: executable examples
Pharmaceuticals: generated form
Pharmaceuticals: generated table
Pharmaceuticals: verified transition
Pharmaceuticals: agent tool
Pharmaceuticals: regression gate
HR and hiring: deterministic calculation
HR and hiring: command eligibility
HR and hiring: executable examples
HR and hiring: generated form
HR and hiring: generated table
HR and hiring: verified transition
HR and hiring: agent tool
HR and hiring: regression gate
SaaS: deterministic calculation
SaaS: command eligibility
SaaS: executable examples
SaaS: generated form
SaaS: generated table
SaaS: verified transition
SaaS: agent tool
SaaS: regression gate
Customer support: deterministic calculation
Customer support: command eligibility
Customer support: executable examples
Customer support: generated form
Customer support: generated table
Customer support: verified transition
Customer support: agent tool
Customer support: regression gate
Education: deterministic calculation
Education: command eligibility
Education: executable examples
Education: generated form
Education: generated table
Education: verified transition
Education: agent tool
Education: regression gate
Travel: deterministic calculation
Travel: command eligibility
Travel: executable examples
Travel: generated form
Travel: generated table
Travel: verified transition
Travel: agent tool
Travel: regression gate
Hospitality: deterministic calculation
Hospitality: command eligibility
Hospitality: executable examples
Hospitality: generated form
Hospitality: generated table
Hospitality: verified transition
Hospitality: agent tool
Hospitality: regression gate
Real estate: deterministic calculation
Real estate: command eligibility
Real estate: executable examples
Real estate: generated form
Real estate: generated table
Real estate: verified transition
Real estate: agent tool
Real estate: regression gate
Manufacturing: deterministic calculation
Manufacturing: command eligibility
Manufacturing: executable examples
Manufacturing: generated form
Manufacturing: generated table
Manufacturing: verified transition
Manufacturing: agent tool
Manufacturing: regression gate
Energy: deterministic calculation
Energy: command eligibility
Energy: executable examples
Energy: generated form
Energy: generated table
Energy: verified transition
Energy: agent tool
Energy: regression gate
Telecom: deterministic calculation
Telecom: command eligibility
Telecom: executable examples
Telecom: generated form
Telecom: generated table
Telecom: verified transition
Telecom: agent tool
Telecom: regression gate
Media: deterministic calculation
Media: command eligibility
Media: executable examples
Media: generated form
Media: generated table
Media: verified transition
Media: agent tool
Media: regression gate
Advertising: deterministic calculation
Advertising: command eligibility
Advertising: executable examples
Advertising: generated form
Advertising: generated table
Advertising: verified transition
Advertising: agent tool
Advertising: regression gate
Public services: deterministic calculation
Public services: command eligibility
Public services: executable examples
Public services: generated form
Public services: generated table
Public services: verified transition
Public services: agent tool
Public services: regression gate
Information security: deterministic calculation
Information security: command eligibility
Information security: executable examples
Information security: generated form
Information security: generated table
Information security: verified transition
Information security: agent tool
Information security: regression gate
DevOps and SRE: deterministic calculation
DevOps and SRE: command eligibility
DevOps and SRE: executable examples
DevOps and SRE: generated form
DevOps and SRE: generated table
DevOps and SRE: verified transition
DevOps and SRE: agent tool
DevOps and SRE: regression gate
Agriculture: deterministic calculation
Agriculture: command eligibility
Agriculture: executable examples
Agriculture: generated form
Agriculture: generated table
Agriculture: verified transition
Agriculture: agent tool
Agriculture: regression gate
Legal and compliance: deterministic calculation
Legal and compliance: command eligibility
Legal and compliance: executable examples
Legal and compliance: generated form
Legal and compliance: generated table
Legal and compliance: verified transition
Legal and compliance: agent tool
Legal and compliance: regression gate
E-commerce operations: deterministic calculation
E-commerce operations: command eligibility
E-commerce operations: executable examples
E-commerce operations: generated form
E-commerce operations: generated table
E-commerce operations: verified transition
E-commerce operations: agent tool
E-commerce operations: regression gate
Accounting: deterministic calculation
Accounting: command eligibility
Accounting: executable examples
Accounting: generated form
Accounting: generated table
Accounting: verified transition
Accounting: agent tool
Accounting: regression gate
Procurement: deterministic calculation
Procurement: command eligibility
Procurement: executable examples
Procurement: generated form
Procurement: generated table
Procurement: verified transition
Procurement: agent tool
Procurement: regression gate
Identity and access: deterministic calculation
Identity and access: command eligibility
Identity and access: executable examples
Identity and access: generated form
Identity and access: generated table
Identity and access: verified transition
Identity and access: agent tool
Identity and access: regression gate
По этим фильтрам кейсов нет. Попробуйте убрать один из фильтров.
Цена интеграции
Baseline: Apple M1 Max, Node.js 24.6.0. Это microbenchmark рантайма FTS, а не обещание времени Vite, webpack или полного tsc.
| Операция | 10 элементов | 100 | 1000 |
|---|---|---|---|
| Compile | 0.0511 ms | 0.3937 ms | 4.3608 ms |
| Validate | 0.0102 ms | 0.0883 ms | 1.0154 ms |
| Execute | 0.0010 ms | 0.0021 ms | 0.0157 ms |
| Generate TypeScript | 0.0065 ms | 0.0392 ms | 0.3704 ms |
| Transpile generated TS | 1.8410 ms | 3.6606 ms | 22.9837 ms |
При масштабе 1000 compile + validate занимает около 5.38 ms на этой машине. В курсе есть воспроизводимый harness и методика сравнения before/after.
Новый полный курс
23 последовательных модулей: синтаксис, утилиты, новые unit-тесты, диагностика компилятора, Node.js, React, DDD, миграция легаси, версии правил, AI/MCP, CI, benchmark и научные границы доказательства. В каждом модуле — упражнения, которые выполняются прямо в песочнице.
Ступени, треки и материалы портала. Кликните ступень, чтобы раскрыть треки, трек — чтобы увидеть статьи, статью — чтобы увидеть, с чем она связана по общим темам.
Колесо — масштаб, перетаскивание — панорама. Стрелки и Tab — переход между узлами, Enter — раскрыть или выбрать. Повторный клик по материалу показывает его соседей по общим темам.